From c5a40aaf92f26f5d48e08ccf99feacf6fcc26235 Mon Sep 17 00:00:00 2001 From: Miles Johnson Date: Mon, 27 Jul 2026 15:37:04 -0700 Subject: [PATCH 01/78] fix(rust): Fix docker scaffolding not cleaning empty lib/main files. (#170) --- toolchains/rust/CHANGELOG.md | 6 ++++++ toolchains/rust/src/tier1.rs | 15 ++++++++++++--- toolchains/rust/tests/tier1_test.rs | 24 ++++++++++++++++++++++++ 3 files changed, 42 insertions(+), 3 deletions(-) diff --git a/toolchains/rust/CHANGELOG.md b/toolchains/rust/CHANGELOG.md index 999c4b62..c5967e09 100644 --- a/toolchains/rust/CHANGELOG.md +++ b/toolchains/rust/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## Unreleased + +#### 🐞 Fixes + +- Fixed an issue where Docker scaffolding would leave behind empty `lib.rs` or `main.rs` files. + ## 1.0.6 #### 🐞 Fixes diff --git a/toolchains/rust/src/tier1.rs b/toolchains/rust/src/tier1.rs index b5182e50..b4c219d0 100644 --- a/toolchains/rust/src/tier1.rs +++ b/toolchains/rust/src/tier1.rs @@ -86,15 +86,14 @@ pub fn scaffold_docker( Json(input): Json, ) -> FnResult> { let mut output = ScaffoldDockerOutput::default(); + let lib_file = input.output_dir.join("src/lib.rs"); + let main_file = input.output_dir.join("src/main.rs"); // Cargo requires either `lib.rs` or `main.rs` during // the workspace/configs phase, which isn't copied till the // sources phase. Because scaffolding may attempt to run // Cargo commands, it will fail without these files! if input.phase == ScaffoldDockerPhase::Configs && input.project.is_some() { - let lib_file = input.output_dir.join("src/lib.rs"); - let main_file = input.output_dir.join("src/main.rs"); - fs::write_file(&lib_file, "")?; fs::write_file(&main_file, "")?; @@ -107,6 +106,16 @@ pub fn scaffold_docker( } } + // When we copy sources, we then need to remove these files + // if they are empty, as to not cause Cargo build issues + if input.phase == ScaffoldDockerPhase::Sources && input.project.is_some() { + for file in [lib_file, main_file] { + if file.exists() && fs::metadata(&file).is_ok_and(|meta| meta.len() == 0) { + let _ = fs::remove_file(file); + } + } + } + Ok(Json(output)) } diff --git a/toolchains/rust/tests/tier1_test.rs b/toolchains/rust/tests/tier1_test.rs index d3b3c9f5..4dcce4d7 100644 --- a/toolchains/rust/tests/tier1_test.rs +++ b/toolchains/rust/tests/tier1_test.rs @@ -95,6 +95,30 @@ mod rust_toolchain_tier1 { assert!(output.copied_files.is_empty()); } + + #[tokio::test(flavor = "multi_thread")] + async fn removes_empty_files_in_sources_phase() { + let sandbox = create_empty_moon_sandbox(); + let plugin = sandbox.create_toolchain("rust").await; + let output_dir = sandbox.path().join("out"); + + fs::create_dir_all(output_dir.join("src")).unwrap(); + fs::write(output_dir.join("src/lib.rs"), "").unwrap(); + fs::write(output_dir.join("src/main.rs"), "fn main() {}").unwrap(); + + plugin + .scaffold_docker(ScaffoldDockerInput { + input_dir: VirtualPath::Real(sandbox.path().join("in")), + output_dir: VirtualPath::Real(output_dir.clone()), + phase: ScaffoldDockerPhase::Sources, + project: Some(ProjectFragment::default()), + ..Default::default() + }) + .await; + + assert!(!output_dir.join("src/lib.rs").exists()); + assert!(output_dir.join("src/main.rs").exists()); + } } mod prune_docker { From d8ec6432f338ddf6c44eb5e73687834fd94397ea Mon Sep 17 00:00:00 2001 From: Miles Johnson Date: Mon, 27 Jul 2026 15:38:18 -0700 Subject: [PATCH 02/78] chore: Release --- Cargo.lock | 2 +- toolchains/rust/CHANGELOG.md | 2 +- toolchains/rust/Cargo.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index dc236ffc..83aecfc1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4790,7 +4790,7 @@ dependencies = [ [[package]] name = "rust_toolchain" -version = "1.0.6" +version = "1.0.7" dependencies = [ "cargo-lock", "cargo_toml", diff --git a/toolchains/rust/CHANGELOG.md b/toolchains/rust/CHANGELOG.md index c5967e09..4041564f 100644 --- a/toolchains/rust/CHANGELOG.md +++ b/toolchains/rust/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## Unreleased +## 1.0.7 #### 🐞 Fixes diff --git a/toolchains/rust/Cargo.toml b/toolchains/rust/Cargo.toml index 95ecb701..8189be13 100644 --- a/toolchains/rust/Cargo.toml +++ b/toolchains/rust/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rust_toolchain" -version = "1.0.6" +version = "1.0.7" edition = "2024" description = "Rust toolchain WASM plugin for moon." authors = ["Miles Johnson"] From 55eec502c8e027aa606a09dcca5ba6a6b0c5f14e Mon Sep 17 00:00:00 2001 From: Alex Launi Date: Sun, 2 Aug 2026 16:46:43 -0400 Subject: [PATCH 03/78] feat: use prebuilt jdx Ruby binaries if available (#171) --- scripts/generateRubyReleases.mjs | 70 ++++++++++ tools/ruby/CHANGELOG.md | 6 + tools/ruby/README.md | 6 + tools/ruby/releases.json | 227 +++++++++++++++++++++++++++++++ tools/ruby/src/proto.rs | 177 +++++++++++++++++++++++- 5 files changed, 485 insertions(+), 1 deletion(-) create mode 100644 scripts/generateRubyReleases.mjs create mode 100644 tools/ruby/releases.json diff --git a/scripts/generateRubyReleases.mjs b/scripts/generateRubyReleases.mjs new file mode 100644 index 00000000..9048680d --- /dev/null +++ b/scripts/generateRubyReleases.mjs @@ -0,0 +1,70 @@ +// @ts-check +import fs from "node:fs"; + +const GH_TOKEN = process.env.GITHUB_TOKEN || process.env.GH_TOKEN; +const RELEASES_PATH = "tools/ruby/releases.json"; +const data = fs.existsSync(RELEASES_PATH) + ? JSON.parse(fs.readFileSync(RELEASES_PATH, "utf8")) + : {}; + +const headers = { + Accept: "application/vnd.github+json", + ...(GH_TOKEN ? { Authorization: `Bearer ${GH_TOKEN}` } : {}), +}; + +let page = 1; + +while (true) { + console.log(`Loading page ${page}`); + + const response = await fetch( + `https://api.github.com/repos/jdx/ruby/releases?per_page=100&page=${page}`, + { headers }, + ); + + if (!response.ok) { + throw new Error(`GitHub API returned HTTP ${response.status}`); + } + + const releases = await response.json(); + + for (const release of releases) { + const version = release.tag_name; + + // Ignore immutable build revisions, such as 3.4.9-1. + if (!/^\d+\.\d+\.\d+(?:-(?:preview|rc)\d+)?$/.test(version)) { + continue; + } + + for (const asset of release.assets) { + const match = asset.name.match( + /^ruby-(.+)\.(macos|x86_64_linux|arm64_linux)\.tar\.gz$/, + ); + + if (!match || match[1] !== version) { + continue; + } + + data[version] ||= {}; + data[version][match[2]] = asset.name; + } + } + + const link = response.headers.get("link"); + + if (!link?.includes('rel="next"')) { + break; + } + + page += 1; +} + +function sortObjectKeys(value) { + return Object.fromEntries( + Object.entries(value) + .sort(([a], [b]) => a.localeCompare(b, undefined, { numeric: true })) + .map(([key, item]) => [key, typeof item === "object" ? sortObjectKeys(item) : item]), + ); +} + +fs.writeFileSync(RELEASES_PATH, `${JSON.stringify(sortObjectKeys(data), null, 2)}\n`); diff --git a/tools/ruby/CHANGELOG.md b/tools/ruby/CHANGELOG.md index 0fb6dfc9..44c68498 100644 --- a/tools/ruby/CHANGELOG.md +++ b/tools/ruby/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## Unreleased + +#### 🚀 Updates + +- Added support for installing portable Ruby binaries from `jdx/ruby` when available. + ## 0.2.8 #### 🚀 Updates diff --git a/tools/ruby/README.md b/tools/ruby/README.md index e869c343..e889b065 100644 --- a/tools/ruby/README.md +++ b/tools/ruby/README.md @@ -37,3 +37,9 @@ Test the plugins by running `proto` commands. proto install ruby-test proto versions ruby-test ``` + +Update the cached list of portable Ruby binaries after jdx publishes new releases: + +```shell +node scripts/generateRubyReleases.mjs +``` diff --git a/tools/ruby/releases.json b/tools/ruby/releases.json new file mode 100644 index 00000000..f88d15a5 --- /dev/null +++ b/tools/ruby/releases.json @@ -0,0 +1,227 @@ +{ + "3.2.1": { + "arm64_linux": "ruby-3.2.1.arm64_linux.tar.gz", + "macos": "ruby-3.2.1.macos.tar.gz", + "x86_64_linux": "ruby-3.2.1.x86_64_linux.tar.gz" + }, + "3.2.2": { + "arm64_linux": "ruby-3.2.2.arm64_linux.tar.gz", + "macos": "ruby-3.2.2.macos.tar.gz", + "x86_64_linux": "ruby-3.2.2.x86_64_linux.tar.gz" + }, + "3.2.3": { + "arm64_linux": "ruby-3.2.3.arm64_linux.tar.gz", + "macos": "ruby-3.2.3.macos.tar.gz", + "x86_64_linux": "ruby-3.2.3.x86_64_linux.tar.gz" + }, + "3.2.4": { + "arm64_linux": "ruby-3.2.4.arm64_linux.tar.gz", + "macos": "ruby-3.2.4.macos.tar.gz", + "x86_64_linux": "ruby-3.2.4.x86_64_linux.tar.gz" + }, + "3.2.5": { + "arm64_linux": "ruby-3.2.5.arm64_linux.tar.gz", + "macos": "ruby-3.2.5.macos.tar.gz", + "x86_64_linux": "ruby-3.2.5.x86_64_linux.tar.gz" + }, + "3.2.6": { + "arm64_linux": "ruby-3.2.6.arm64_linux.tar.gz", + "macos": "ruby-3.2.6.macos.tar.gz", + "x86_64_linux": "ruby-3.2.6.x86_64_linux.tar.gz" + }, + "3.2.7": { + "arm64_linux": "ruby-3.2.7.arm64_linux.tar.gz", + "macos": "ruby-3.2.7.macos.tar.gz", + "x86_64_linux": "ruby-3.2.7.x86_64_linux.tar.gz" + }, + "3.2.8": { + "arm64_linux": "ruby-3.2.8.arm64_linux.tar.gz", + "macos": "ruby-3.2.8.macos.tar.gz", + "x86_64_linux": "ruby-3.2.8.x86_64_linux.tar.gz" + }, + "3.2.9": { + "arm64_linux": "ruby-3.2.9.arm64_linux.tar.gz", + "macos": "ruby-3.2.9.macos.tar.gz", + "x86_64_linux": "ruby-3.2.9.x86_64_linux.tar.gz" + }, + "3.2.10": { + "arm64_linux": "ruby-3.2.10.arm64_linux.tar.gz", + "macos": "ruby-3.2.10.macos.tar.gz", + "x86_64_linux": "ruby-3.2.10.x86_64_linux.tar.gz" + }, + "3.2.11": { + "arm64_linux": "ruby-3.2.11.arm64_linux.tar.gz", + "macos": "ruby-3.2.11.macos.tar.gz", + "x86_64_linux": "ruby-3.2.11.x86_64_linux.tar.gz" + }, + "3.3.0": { + "arm64_linux": "ruby-3.3.0.arm64_linux.tar.gz", + "macos": "ruby-3.3.0.macos.tar.gz", + "x86_64_linux": "ruby-3.3.0.x86_64_linux.tar.gz" + }, + "3.3.1": { + "arm64_linux": "ruby-3.3.1.arm64_linux.tar.gz", + "macos": "ruby-3.3.1.macos.tar.gz", + "x86_64_linux": "ruby-3.3.1.x86_64_linux.tar.gz" + }, + "3.3.2": { + "arm64_linux": "ruby-3.3.2.arm64_linux.tar.gz", + "macos": "ruby-3.3.2.macos.tar.gz", + "x86_64_linux": "ruby-3.3.2.x86_64_linux.tar.gz" + }, + "3.3.3": { + "arm64_linux": "ruby-3.3.3.arm64_linux.tar.gz", + "macos": "ruby-3.3.3.macos.tar.gz", + "x86_64_linux": "ruby-3.3.3.x86_64_linux.tar.gz" + }, + "3.3.4": { + "arm64_linux": "ruby-3.3.4.arm64_linux.tar.gz", + "macos": "ruby-3.3.4.macos.tar.gz", + "x86_64_linux": "ruby-3.3.4.x86_64_linux.tar.gz" + }, + "3.3.5": { + "arm64_linux": "ruby-3.3.5.arm64_linux.tar.gz", + "macos": "ruby-3.3.5.macos.tar.gz", + "x86_64_linux": "ruby-3.3.5.x86_64_linux.tar.gz" + }, + "3.3.6": { + "arm64_linux": "ruby-3.3.6.arm64_linux.tar.gz", + "macos": "ruby-3.3.6.macos.tar.gz", + "x86_64_linux": "ruby-3.3.6.x86_64_linux.tar.gz" + }, + "3.3.7": { + "arm64_linux": "ruby-3.3.7.arm64_linux.tar.gz", + "macos": "ruby-3.3.7.macos.tar.gz", + "x86_64_linux": "ruby-3.3.7.x86_64_linux.tar.gz" + }, + "3.3.8": { + "arm64_linux": "ruby-3.3.8.arm64_linux.tar.gz", + "macos": "ruby-3.3.8.macos.tar.gz", + "x86_64_linux": "ruby-3.3.8.x86_64_linux.tar.gz" + }, + "3.3.9": { + "arm64_linux": "ruby-3.3.9.arm64_linux.tar.gz", + "macos": "ruby-3.3.9.macos.tar.gz", + "x86_64_linux": "ruby-3.3.9.x86_64_linux.tar.gz" + }, + "3.3.10": { + "arm64_linux": "ruby-3.3.10.arm64_linux.tar.gz", + "macos": "ruby-3.3.10.macos.tar.gz", + "x86_64_linux": "ruby-3.3.10.x86_64_linux.tar.gz" + }, + "3.3.11": { + "arm64_linux": "ruby-3.3.11.arm64_linux.tar.gz", + "macos": "ruby-3.3.11.macos.tar.gz", + "x86_64_linux": "ruby-3.3.11.x86_64_linux.tar.gz" + }, + "3.3.12": { + "arm64_linux": "ruby-3.3.12.arm64_linux.tar.gz", + "macos": "ruby-3.3.12.macos.tar.gz", + "x86_64_linux": "ruby-3.3.12.x86_64_linux.tar.gz" + }, + "3.4.0": { + "arm64_linux": "ruby-3.4.0.arm64_linux.tar.gz", + "macos": "ruby-3.4.0.macos.tar.gz", + "x86_64_linux": "ruby-3.4.0.x86_64_linux.tar.gz" + }, + "3.4.1": { + "arm64_linux": "ruby-3.4.1.arm64_linux.tar.gz", + "macos": "ruby-3.4.1.macos.tar.gz", + "x86_64_linux": "ruby-3.4.1.x86_64_linux.tar.gz" + }, + "3.4.2": { + "arm64_linux": "ruby-3.4.2.arm64_linux.tar.gz", + "macos": "ruby-3.4.2.macos.tar.gz", + "x86_64_linux": "ruby-3.4.2.x86_64_linux.tar.gz" + }, + "3.4.3": { + "arm64_linux": "ruby-3.4.3.arm64_linux.tar.gz", + "macos": "ruby-3.4.3.macos.tar.gz", + "x86_64_linux": "ruby-3.4.3.x86_64_linux.tar.gz" + }, + "3.4.4": { + "arm64_linux": "ruby-3.4.4.arm64_linux.tar.gz", + "macos": "ruby-3.4.4.macos.tar.gz", + "x86_64_linux": "ruby-3.4.4.x86_64_linux.tar.gz" + }, + "3.4.5": { + "arm64_linux": "ruby-3.4.5.arm64_linux.tar.gz", + "macos": "ruby-3.4.5.macos.tar.gz", + "x86_64_linux": "ruby-3.4.5.x86_64_linux.tar.gz" + }, + "3.4.6": { + "arm64_linux": "ruby-3.4.6.arm64_linux.tar.gz", + "macos": "ruby-3.4.6.macos.tar.gz", + "x86_64_linux": "ruby-3.4.6.x86_64_linux.tar.gz" + }, + "3.4.7": { + "arm64_linux": "ruby-3.4.7.arm64_linux.tar.gz", + "macos": "ruby-3.4.7.macos.tar.gz", + "x86_64_linux": "ruby-3.4.7.x86_64_linux.tar.gz" + }, + "3.4.8": { + "arm64_linux": "ruby-3.4.8.arm64_linux.tar.gz", + "macos": "ruby-3.4.8.macos.tar.gz", + "x86_64_linux": "ruby-3.4.8.x86_64_linux.tar.gz" + }, + "3.4.9": { + "arm64_linux": "ruby-3.4.9.arm64_linux.tar.gz", + "macos": "ruby-3.4.9.macos.tar.gz", + "x86_64_linux": "ruby-3.4.9.x86_64_linux.tar.gz" + }, + "3.4.10": { + "arm64_linux": "ruby-3.4.10.arm64_linux.tar.gz", + "macos": "ruby-3.4.10.macos.tar.gz", + "x86_64_linux": "ruby-3.4.10.x86_64_linux.tar.gz" + }, + "3.5.0-preview1": { + "arm64_linux": "ruby-3.5.0-preview1.arm64_linux.tar.gz", + "macos": "ruby-3.5.0-preview1.macos.tar.gz", + "x86_64_linux": "ruby-3.5.0-preview1.x86_64_linux.tar.gz" + }, + "4.0.0": { + "arm64_linux": "ruby-4.0.0.arm64_linux.tar.gz", + "macos": "ruby-4.0.0.macos.tar.gz", + "x86_64_linux": "ruby-4.0.0.x86_64_linux.tar.gz" + }, + "4.0.0-preview2": { + "arm64_linux": "ruby-4.0.0-preview2.arm64_linux.tar.gz", + "macos": "ruby-4.0.0-preview2.macos.tar.gz", + "x86_64_linux": "ruby-4.0.0-preview2.x86_64_linux.tar.gz" + }, + "4.0.0-preview3": { + "arm64_linux": "ruby-4.0.0-preview3.arm64_linux.tar.gz", + "macos": "ruby-4.0.0-preview3.macos.tar.gz", + "x86_64_linux": "ruby-4.0.0-preview3.x86_64_linux.tar.gz" + }, + "4.0.1": { + "arm64_linux": "ruby-4.0.1.arm64_linux.tar.gz", + "macos": "ruby-4.0.1.macos.tar.gz", + "x86_64_linux": "ruby-4.0.1.x86_64_linux.tar.gz" + }, + "4.0.2": { + "arm64_linux": "ruby-4.0.2.arm64_linux.tar.gz", + "macos": "ruby-4.0.2.macos.tar.gz", + "x86_64_linux": "ruby-4.0.2.x86_64_linux.tar.gz" + }, + "4.0.3": { + "arm64_linux": "ruby-4.0.3.arm64_linux.tar.gz", + "macos": "ruby-4.0.3.macos.tar.gz", + "x86_64_linux": "ruby-4.0.3.x86_64_linux.tar.gz" + }, + "4.0.4": { + "arm64_linux": "ruby-4.0.4.arm64_linux.tar.gz", + "macos": "ruby-4.0.4.macos.tar.gz", + "x86_64_linux": "ruby-4.0.4.x86_64_linux.tar.gz" + }, + "4.0.5": { + "arm64_linux": "ruby-4.0.5.arm64_linux.tar.gz", + "macos": "ruby-4.0.5.macos.tar.gz", + "x86_64_linux": "ruby-4.0.5.x86_64_linux.tar.gz" + }, + "4.0.6": { + "arm64_linux": "ruby-4.0.6.arm64_linux.tar.gz", + "macos": "ruby-4.0.6.macos.tar.gz", + "x86_64_linux": "ruby-4.0.6.x86_64_linux.tar.gz" + } +} diff --git a/tools/ruby/src/proto.rs b/tools/ruby/src/proto.rs index 14edaa9e..fe1c7d68 100644 --- a/tools/ruby/src/proto.rs +++ b/tools/ruby/src/proto.rs @@ -1,8 +1,16 @@ use extism_pdk::*; use proto_pdk::*; -use std::collections::HashMap; +use std::collections::{BTreeMap, HashMap}; use tool_common::enable_tracing; +type PrebuiltReleases = BTreeMap>; + +#[derive(Debug, PartialEq)] +struct PrebuiltAsset { + filename: String, + url: String, +} + #[host_fn] extern "ExtismHost" { fn exec_command(input: Json) -> Json; @@ -68,6 +76,13 @@ pub fn build_instructions( return Err(PluginError::UnsupportedWindowsBuild.into()); } + if let Some(source) = find_prebuilt_source(&env, &version)? { + return Ok(Json(BuildInstructionsOutput { + source: Some(source), + ..BuildInstructionsOutput::default() + })); + } + let output = BuildInstructionsOutput { help_url: Some( "https://github.com/rbenv/ruby-build/wiki".into(), @@ -124,6 +139,81 @@ pub fn build_instructions( Ok(Json(output)) } +fn find_prebuilt_source( + env: &HostEnvironment, + version: &VersionSpec, +) -> AnyResult> { + let version = version.to_string(); + + Ok(load_prebuilt_asset(env, &version)?.map(|asset| { + SourceLocation::Archive(ArchiveSource { + url: asset.url, + prefix: Some(format!("ruby-{version}")), + }) + })) +} + +#[plugin_fn] +pub fn download_prebuilt( + Json(input): Json, +) -> FnResult> { + let env = get_host_environment()?; + let version = input.context.version.to_string(); + + let Some(asset) = load_prebuilt_asset(&env, &version)? else { + return Err(plugin_err!( + "No pre-built available for Ruby {version} on {}-{}! Try building from source with --build.", + env.os, + env.arch, + )); + }; + + Ok(Json(create_download_output(asset, &version))) +} + +fn load_prebuilt_asset(env: &HostEnvironment, version: &str) -> AnyResult> { + let Some(platform) = get_prebuilt_platform(env) else { + return Ok(None); + }; + + let releases: PrebuiltReleases = fetch_json( + "https://raw.githubusercontent.com/moonrepo/plugins/master/tools/ruby/releases.json", + )?; + + Ok(select_prebuilt_asset(&releases, platform, version)) +} + +fn select_prebuilt_asset( + releases: &PrebuiltReleases, + platform: &str, + version: &str, +) -> Option { + let filename = releases.get(version)?.get(platform)?; + + Some(PrebuiltAsset { + filename: filename.to_owned(), + url: format!("https://github.com/jdx/ruby/releases/download/{version}/{filename}"), + }) +} + +fn create_download_output(asset: PrebuiltAsset, version: &str) -> DownloadPrebuiltOutput { + DownloadPrebuiltOutput { + archive_prefix: Some(format!("ruby-{version}")), + download_name: Some(asset.filename), + download_url: asset.url, + ..DownloadPrebuiltOutput::default() + } +} + +fn get_prebuilt_platform(env: &HostEnvironment) -> Option<&'static str> { + match (env.os, env.arch) { + (HostOS::Linux, HostArch::X64) => Some("x86_64_linux"), + (HostOS::Linux, HostArch::Arm64) => Some("arm64_linux"), + (HostOS::MacOS, HostArch::Arm64) => Some("macos"), + _ => None, + } +} + #[plugin_fn] pub fn locate_executables( Json(_): Json, @@ -158,3 +248,88 @@ pub fn locate_executables( ..LocateExecutablesOutput::default() })) } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn maps_jdx_supported_platforms() { + for (os, arch, expected) in [ + (HostOS::Linux, HostArch::X64, Some("x86_64_linux")), + (HostOS::Linux, HostArch::Arm64, Some("arm64_linux")), + (HostOS::MacOS, HostArch::Arm64, Some("macos")), + (HostOS::MacOS, HostArch::X64, None), + (HostOS::Windows, HostArch::X64, None), + ] { + assert_eq!( + get_prebuilt_platform(&HostEnvironment { + os, + arch, + ..HostEnvironment::default() + }), + expected + ); + } + } + + #[test] + fn selects_matching_release_asset() { + let asset = select_prebuilt_asset( + &BTreeMap::from_iter([( + "3.4.9".into(), + BTreeMap::from_iter([( + "arm64_linux".into(), + "ruby-3.4.9.arm64_linux.tar.gz".into(), + )]), + )]), + "arm64_linux", + "3.4.9", + ); + + assert_eq!( + asset, + Some(PrebuiltAsset { + filename: "ruby-3.4.9.arm64_linux.tar.gz".into(), + url: "https://github.com/jdx/ruby/releases/download/3.4.9/ruby-3.4.9.arm64_linux.tar.gz".into(), + }) + ); + } + + #[test] + fn skips_release_without_matching_asset() { + let asset = select_prebuilt_asset( + &BTreeMap::from_iter([("3.4.9".into(), BTreeMap::new())]), + "macos", + "3.4.9", + ); + + assert_eq!(asset, None); + } + + #[test] + fn skips_missing_release() { + let asset = select_prebuilt_asset(&BTreeMap::new(), "macos", "3.1.0"); + + assert_eq!(asset, None); + } + + #[test] + fn creates_download_output() { + assert_eq!( + create_download_output( + PrebuiltAsset { + filename: "ruby-3.4.9.macos.tar.gz".into(), + url: "https://example.com/ruby-3.4.9.macos.tar.gz".into(), + }, + "3.4.9", + ), + DownloadPrebuiltOutput { + archive_prefix: Some("ruby-3.4.9".into()), + download_name: Some("ruby-3.4.9.macos.tar.gz".into()), + download_url: "https://example.com/ruby-3.4.9.macos.tar.gz".into(), + ..DownloadPrebuiltOutput::default() + } + ); + } +} From 3bb121eb4e56fb97b1e35711b885433f90367f3e Mon Sep 17 00:00:00 2001 From: Miles Johnson Date: Mon, 3 Aug 2026 14:09:28 -0700 Subject: [PATCH 04/78] breaking: Upgrade to proto v0.60 APIs. (#173) --- Cargo.lock | 128 ++++++++----- Cargo.toml | 6 +- backends/asdf/CHANGELOG.md | 6 + backends/asdf/src/config.rs | 10 +- backends/asdf/src/proto.rs | 57 +++--- backends/cargo/CHANGELOG.md | 6 + backends/cargo/src/proto.rs | 28 +-- backends/cargo/tests/download_test.rs | 18 +- backends/cargo/tests/locate_test.rs | 28 ++- backends/npm/CHANGELOG.md | 6 + backends/npm/src/proto.rs | 15 +- backends/npm/tests/locate_test.rs | 60 ++++-- extensions/migrate-nx/src/nx_migrator.rs | 8 +- toolchains/python/tests/tier2_test.rs | 8 +- .../typescript/tests/tsconfig_json_test.rs | 24 +-- tools/bun/CHANGELOG.md | 6 + tools/bun/src/proto.rs | 14 +- tools/bun/tests/versions_test.rs | 16 +- tools/deno/CHANGELOG.md | 6 + tools/deno/src/proto.rs | 12 +- tools/example/src/proto.rs | 2 +- tools/go/CHANGELOG.md | 6 + tools/go/src/proto.rs | 14 +- tools/internal-schema/CHANGELOG.md | 6 + tools/internal-schema/src/proto.rs | 27 ++- tools/java/CHANGELOG.md | 6 + tools/java/src/foojay.rs | 4 +- tools/java/src/java.rs | 2 +- tools/java/src/lib.rs | 4 +- tools/java/src/proto.rs | 12 +- tools/java/tests/download_test.rs | 5 +- tools/java/tests/metadata_test.rs | 1 - tools/moon/CHANGELOG.md | 6 + tools/moon/src/proto.rs | 8 +- tools/node-depman/CHANGELOG.md | 6 + tools/node-depman/src/proto.rs | 20 +- tools/node-depman/tests/activate_test.rs | 48 +++-- tools/node-depman/tests/download_test.rs | 29 ++- tools/node-depman/tests/versions_test.rs | 20 +- tools/node/CHANGELOG.md | 6 + tools/node/src/proto.rs | 12 +- tools/node/tests/versions_test.rs | 16 +- tools/proto/CHANGELOG.md | 6 + tools/proto/src/proto.rs | 12 +- tools/python-poetry/CHANGELOG.md | 6 + tools/python-poetry/src/proto.rs | 21 ++- tools/python-uv/CHANGELOG.md | 6 + tools/python-uv/src/proto.rs | 10 +- tools/python-uv/tests/download_test.rs | 10 +- tools/python/CHANGELOG.md | 6 + tools/python/src/proto.rs | 12 +- tools/ruby/CHANGELOG.md | 1 + tools/ruby/src/lib.rs | 2 + tools/ruby/src/proto.rs | 178 ++---------------- tools/ruby/src/releases.rs | 148 +++++++++++++++ tools/rust/CHANGELOG.md | 6 + tools/rust/src/helpers.rs | 13 +- tools/rust/src/proto.rs | 32 ++-- 58 files changed, 689 insertions(+), 502 deletions(-) create mode 100644 tools/ruby/src/releases.rs diff --git a/Cargo.lock b/Cargo.lock index 83aecfc1..3fd67558 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -87,7 +87,7 @@ dependencies = [ "backend_common", "extism-pdk", "proto_pdk", - "proto_pdk_test_utils 0.47.0", + "proto_pdk_test_utils 0.48.0", "rustc-hash", "schematic", "serde", @@ -272,7 +272,7 @@ dependencies = [ "lang_javascript_common", "nodejs_package_json", "proto_pdk", - "proto_pdk_test_utils 0.47.0", + "proto_pdk_test_utils 0.48.0", "schematic", "serde", "starbase_sandbox 0.11.1", @@ -455,7 +455,7 @@ dependencies = [ "backend_common", "extism-pdk", "proto_pdk", - "proto_pdk_test_utils 0.47.0", + "proto_pdk_test_utils 0.48.0", "rustc-hash", "schematic", "serde", @@ -1204,7 +1204,7 @@ version = "0.15.10" dependencies = [ "extism-pdk", "proto_pdk", - "proto_pdk_test_utils 0.47.0", + "proto_pdk_test_utils 0.48.0", "schematic", "serde", "starbase_sandbox 0.11.1", @@ -1988,8 +1988,8 @@ version = "0.16.7" dependencies = [ "extism-pdk", "proto_pdk", - "proto_pdk_api 0.32.1", - "proto_pdk_test_utils 0.47.0", + "proto_pdk_api 0.33.0", + "proto_pdk_test_utils 0.48.0", "schematic", "serde", "starbase_sandbox 0.11.1", @@ -2596,8 +2596,8 @@ version = "0.1.0" dependencies = [ "extism-pdk", "proto_pdk", - "proto_pdk_api 0.32.1", - "proto_pdk_test_utils 0.47.0", + "proto_pdk_api 0.33.0", + "proto_pdk_test_utils 0.48.0", "schematic", "serde", "serde_json", @@ -2783,7 +2783,7 @@ name = "lang_javascript_common" version = "0.1.0" dependencies = [ "nodejs_package_json", - "proto_pdk_api 0.32.1", + "proto_pdk_api 0.33.0", "serde", "serde_json", "starbase_utils 0.13.8", @@ -3145,7 +3145,7 @@ dependencies = [ "serde", "serde_json", "version_spec 0.10.3", - "warpgate_api", + "warpgate_api 0.17.6", ] [[package]] @@ -3185,7 +3185,7 @@ dependencies = [ "rustc-hash", "schematic", "serde", - "warpgate_pdk", + "warpgate_pdk 0.16.5", ] [[package]] @@ -3204,7 +3204,7 @@ dependencies = [ "schematic", "serde", "serde_json", - "warpgate_api", + "warpgate_api 0.17.6", ] [[package]] @@ -3277,7 +3277,7 @@ version = "0.4.2" dependencies = [ "extism-pdk", "proto_pdk", - "proto_pdk_test_utils 0.47.0", + "proto_pdk_test_utils 0.48.0", "serde", "starbase_sandbox 0.11.1", "tokio", @@ -3310,8 +3310,8 @@ dependencies = [ "nodejs_package_json", "npmrc-config-rs", "proto_pdk", - "proto_pdk_api 0.32.1", - "proto_pdk_test_utils 0.47.0", + "proto_pdk_api 0.33.0", + "proto_pdk_test_utils 0.48.0", "regex", "rustc-hash", "schematic", @@ -3333,7 +3333,7 @@ dependencies = [ "moon_pdk_api", "moon_pdk_test_utils", "node_depman_tool", - "proto_pdk_api 0.32.1", + "proto_pdk_api 0.33.0", "schematic", "serde", "serde_json", @@ -3351,7 +3351,7 @@ dependencies = [ "lang_javascript_common", "nodejs_package_json", "proto_pdk", - "proto_pdk_test_utils 0.47.0", + "proto_pdk_test_utils 0.48.0", "schematic", "serde", "serial_test", @@ -3437,7 +3437,7 @@ dependencies = [ "backend_common", "extism-pdk", "proto_pdk", - "proto_pdk_test_utils 0.47.0", + "proto_pdk_test_utils 0.48.0", "rustc-hash", "schematic", "serde", @@ -4046,9 +4046,9 @@ dependencies = [ [[package]] name = "proto_core" -version = "0.59.0" +version = "0.60.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a1838c7d0223c8403b5353869549e19996952a60e38079c8dcff7eb1d46a5d7" +checksum = "2453dd69084fccfdbff7488d3f9c6fe20b1f7bd00f87069db478a2e87a700b8f" dependencies = [ "ai_env", "convert_case 0.11.0", @@ -4060,7 +4060,7 @@ dependencies = [ "minisign-verify", "oci-client 0.17.0", "once_cell", - "proto_pdk_api 0.32.1", + "proto_pdk_api 0.33.0", "proto_shim", "regex", "reqwest", @@ -4083,20 +4083,20 @@ dependencies = [ "url", "uuid", "version_spec 0.11.2", - "warpgate 0.34.0", + "warpgate 0.35.0", ] [[package]] name = "proto_pdk" -version = "0.33.0" +version = "0.34.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0030c90652835da05c1839ebb5e7cb8995bfae776bf340f5869aff530a230ea" +checksum = "7c6984c7bc56c118dad0f597bfbce17429831b46f27f88c54e2367ff671fbdc3" dependencies = [ "extism-pdk", - "proto_pdk_api 0.32.1", + "proto_pdk_api 0.33.0", "rustc-hash", "serde", - "warpgate_pdk", + "warpgate_pdk 0.17.0", ] [[package]] @@ -4114,14 +4114,14 @@ dependencies = [ "system_env", "thiserror 2.0.19", "version_spec 0.10.3", - "warpgate_api", + "warpgate_api 0.17.6", ] [[package]] name = "proto_pdk_api" -version = "0.32.1" +version = "0.33.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e00f08f99b3d05d81133b6ebb9077bc6e1b8a2718182892022d657e706161f4e" +checksum = "1ab11b9f81482b9233620631855e59e7761f8c1cabb861eb9853f02b2bdaae58" dependencies = [ "derive_setters", "rustc-hash", @@ -4131,7 +4131,7 @@ dependencies = [ "system_env", "thiserror 2.0.19", "version_spec 0.11.2", - "warpgate_api", + "warpgate_api 0.18.0", ] [[package]] @@ -4151,24 +4151,24 @@ dependencies = [ [[package]] name = "proto_pdk_test_utils" -version = "0.47.0" +version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7ecdd0834f5d2b54ce06beb900b7ef1ae3cd34a2ac3c3146821beb636ab0b47" +checksum = "a2876fdc4762a98c8b98d333cca84eaf46c48afb9795a89fdbca29e7163c3ce6" dependencies = [ "extism", - "proto_core 0.59.0", - "proto_pdk_api 0.32.1", + "proto_core 0.60.0", + "proto_pdk_api 0.33.0", "serde", "serde_json", "starbase_sandbox 0.12.0", - "warpgate 0.34.0", + "warpgate 0.35.0", ] [[package]] name = "proto_shim" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3296719b871c47a3e81b371353cf689a16bf40b8cb5e9a0f3603239218656634" +checksum = "93bf09c099fd93508eceece6742b5d68d5e6c0c0f4804fd35c883f7de5b7fb66" dependencies = [ "dirs", "windows-sys 0.61.2", @@ -4239,7 +4239,7 @@ version = "0.1.8" dependencies = [ "extism-pdk", "proto_pdk", - "proto_pdk_test_utils 0.47.0", + "proto_pdk_test_utils 0.48.0", "serde", "starbase_sandbox 0.11.1", "starbase_utils 0.13.8", @@ -4267,7 +4267,7 @@ version = "0.14.8" dependencies = [ "extism-pdk", "proto_pdk", - "proto_pdk_test_utils 0.47.0", + "proto_pdk_test_utils 0.48.0", "regex", "serde", "starbase_sandbox 0.11.1", @@ -4304,7 +4304,7 @@ version = "0.3.3" dependencies = [ "extism-pdk", "proto_pdk", - "proto_pdk_test_utils 0.47.0", + "proto_pdk_test_utils 0.48.0", "serde", "starbase_sandbox 0.11.1", "tokio", @@ -4735,7 +4735,7 @@ version = "0.2.8" dependencies = [ "extism-pdk", "proto_pdk", - "proto_pdk_test_utils 0.47.0", + "proto_pdk_test_utils 0.48.0", "serde", "starbase_sandbox 0.11.1", "tokio", @@ -4779,7 +4779,7 @@ version = "0.13.9" dependencies = [ "extism-pdk", "proto_pdk", - "proto_pdk_test_utils 0.47.0", + "proto_pdk_test_utils 0.48.0", "serde", "starbase_sandbox 0.11.1", "starbase_utils 0.13.8", @@ -5018,7 +5018,7 @@ version = "0.18.1" dependencies = [ "extism-pdk", "proto_pdk", - "proto_pdk_test_utils 0.47.0", + "proto_pdk_test_utils 0.48.0", "regex", "serde", "serde_json", @@ -5822,9 +5822,9 @@ dependencies = [ [[package]] name = "system_env" -version = "0.10.0" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4d7bf944aae854a6a613b8af7a4d044def0fbbc763dcea7e1acf84e331d3527" +checksum = "325a3f70fd3a025f4b583cd8eaa90fbabf61493f690815d7148df778bb4839ac" dependencies = [ "regex", "schematic", @@ -6608,14 +6608,14 @@ dependencies = [ "tracing", "ureq", "url", - "warpgate_api", + "warpgate_api 0.17.6", ] [[package]] name = "warpgate" -version = "0.34.0" +version = "0.35.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d48f4db46676d43f6f64e6256069fa8d2fd057cc24c0b6f36dace423676f8210" +checksum = "ecc0be0833cee347fe795c880e4d39a0bb1aadac45c367b6ce029000ebf4d0a3" dependencies = [ "async-trait", "compact_str 0.10.0", @@ -6645,7 +6645,7 @@ dependencies = [ "tracing", "ureq", "url", - "warpgate_api", + "warpgate_api 0.18.0", ] [[package]] @@ -6665,17 +6665,45 @@ dependencies = [ "thiserror 2.0.19", ] +[[package]] +name = "warpgate_api" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87641915cab01cbd66a95c6b8cda696c5ae241f19b49ec9118fdc41621e525db" +dependencies = [ + "anyhow", + "derive_setters", + "rustc-hash", + "schematic", + "serde", + "serde_json", + "starbase_id", + "system_env", + "thiserror 2.0.19", +] + [[package]] name = "warpgate_pdk" version = "0.16.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2897a95cca4ca14002c2407a365aa161a43fe4e4935d626c170e8c34552a22a" +dependencies = [ + "extism-pdk", + "serde", + "warpgate_api 0.17.6", +] + +[[package]] +name = "warpgate_pdk" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e59497845a80b314477c9803eb96eaf41574df879b8f269a8c1711da46ed9184" dependencies = [ "extism-pdk", "serde", "tracing", "tracing-subscriber", - "warpgate_api", + "warpgate_api 0.18.0", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 85b115fd..78f4b9c7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -41,9 +41,9 @@ moon_target = { version = "3.0.0" } # moon_target = { path = "../../moon/crates/target" } # proto -proto_pdk = { version = "0.33.0" } -proto_pdk_api = { version = "0.32.0" } -proto_pdk_test_utils = { version = "0.47.0" } +proto_pdk = { version = "0.34.0" } +proto_pdk_api = { version = "0.33.0" } +proto_pdk_test_utils = { version = "0.48.0" } # proto_pdk = { path = "../../proto/crates/pdk" } # proto_pdk_api = { path = "../../proto/crates/pdk-api" } # proto_pdk_test_utils = { path = "../../proto/crates/pdk-test-utils" } diff --git a/backends/asdf/CHANGELOG.md b/backends/asdf/CHANGELOG.md index 741bc923..230b2209 100644 --- a/backends/asdf/CHANGELOG.md +++ b/backends/asdf/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## Unreleased + +#### 🚀 Updates + +- Updated to support proto v0.60 release. + ## 0.3.4 #### 🚀 Updates diff --git a/backends/asdf/src/config.rs b/backends/asdf/src/config.rs index 44243bcd..fb7a45fd 100644 --- a/backends/asdf/src/config.rs +++ b/backends/asdf/src/config.rs @@ -4,7 +4,6 @@ use extism_pdk::*; use proto_pdk::*; use schematic::Schematic; use serde::{Deserialize, Serialize}; -use std::path::PathBuf; #[host_fn] extern "ExtismHost" { @@ -43,14 +42,11 @@ impl AsdfToolConfig { self.get_shortname() } - pub fn get_backend_path(&self) -> AnyResult { - Ok(PathBuf::from(format!( - "/proto/backends/asdf/{}", - self.get_backend_id()? - ))) + pub fn get_backend_path(&self) -> AnyResult { + VirtualPath::create(format!("/proto/backends/asdf/{}", self.get_backend_id()?)) } - pub fn get_script_path(&self, script: &str) -> AnyResult { + pub fn get_script_path(&self, script: &str) -> AnyResult { self.get_backend_path() .map(|path| path.join("bin").join(script)) } diff --git a/backends/asdf/src/proto.rs b/backends/asdf/src/proto.rs index 401a2edd..00649ea6 100644 --- a/backends/asdf/src/proto.rs +++ b/backends/asdf/src/proto.rs @@ -5,13 +5,10 @@ use proto_pdk::*; use rustc_hash::FxHashMap; use schematic::SchemaBuilder; use starbase_utils::fs; -use std::path::{Path, PathBuf}; #[host_fn] extern "ExtismHost" { fn exec_command(input: Json) -> Json; - fn from_virtual_path(path: String) -> String; - fn to_virtual_path(path: String) -> String; fn host_log(input: Json); } @@ -40,20 +37,8 @@ fn cpu_cores() -> AnyResult { Ok(value) } -fn backend_root() -> AnyResult { - if let Some(value) = var::get::("backend_root")? { - return Ok(value.into()); - } - - let root = into_real_path("/proto/backends")?; - - var::set("backend_root", root.to_str().unwrap())?; - - Ok(root) -} - fn create_script_from_context( - virtual_script_path: &Path, + virtual_script_path: &VirtualPath, context: &PluginContext, ) -> AnyResult { create_script( @@ -65,14 +50,14 @@ fn create_script_from_context( } fn create_script_from_unresolved_context( - virtual_script_path: &Path, + virtual_script_path: &VirtualPath, _context: &PluginUnresolvedContext, ) -> AnyResult { create_script(virtual_script_path, None, None, None) } fn create_script( - virtual_script_path: &Path, + virtual_script_path: &VirtualPath, version: Option<&VersionSpec>, tool_dir: Option<&VirtualPath>, temp_dir: Option<&VirtualPath>, @@ -107,12 +92,10 @@ fn create_script( // Resolve the real path since this is executed in the console input.args.push( - match virtual_script_path.strip_prefix("/proto/backends") { - Ok(suffix) => backend_root()?.join(suffix), - Err(_) => into_real_path(virtual_script_path)?, - } - .to_string_lossy() - .to_string(), + virtual_script_path + .to_real_path()? + .expect("Invalid script path!") + .to_string(), ); if let Some(version) = version { @@ -124,16 +107,20 @@ fn create_script( .insert("ASDF_INSTALL_VERSION".into(), version.to_string()); } - if let Some(dir) = tool_dir { + if let Some(dir) = tool_dir + && let Some(dir) = dir.to_real_path()? + { input .env - .insert("ASDF_INSTALL_PATH".into(), dir.real_path_string().unwrap()); + .insert("ASDF_INSTALL_PATH".into(), dir.to_string()); } - if let Some(dir) = temp_dir { + if let Some(dir) = temp_dir + && let Some(dir) = dir.to_real_path()? + { input .env - .insert("ASDF_DOWNLOAD_PATH".into(), dir.real_path_string().unwrap()); + .insert("ASDF_DOWNLOAD_PATH".into(), dir.to_string()); } Ok(input) @@ -177,10 +164,10 @@ pub fn register_tool(Json(input): Json) -> FnResult) -> FnResult Result { let dir = env.home_dir.join(".cargo"); match get_host_env_var("CARGO_HOME")? { - Some(value) => Ok(if value.is_empty() { - dir - } else { - into_virtual_path(value)? - }), + Some(value) => { + if value.is_empty() { + Ok(dir) + } else { + VirtualPath::create(value) + } + } None => Ok(dir), } } @@ -81,7 +83,7 @@ pub fn native_install( let tool_config = get_tool_config::()?; // Detect `cargo-binstall` - let binstall_path = get_cargo_home(&env)? + let binstall_path = get_cargo_home(env)? .join("bin") .join(env.os.get_exe_name("cargo-binstall")); @@ -121,9 +123,13 @@ pub fn native_install( // Where to install command.args.push("--root".into()); - command - .args - .push(input.install_dir.real_path_string().unwrap()); + command.args.push( + input + .install_dir + .to_real_path()? + .expect("Invalid install directory!") + .to_string(), + ); // Other options if input.force { diff --git a/backends/cargo/tests/download_test.rs b/backends/cargo/tests/download_test.rs index 68972ad7..9e5aa53b 100644 --- a/backends/cargo/tests/download_test.rs +++ b/backends/cargo/tests/download_test.rs @@ -35,16 +35,16 @@ mod cargo_backend_download { }); } - mod bin { - use super::*; + // mod bin { + // use super::*; - generate_native_install_tests!("cargo:cargo-outdated", "0.17.0", None, |cfg| { - cfg.tool_config(CargoToolConfig { - bin: Some("cargo-outdated".into()), - ..Default::default() - }); - }); - } + // generate_native_install_tests!("cargo:cargo-outdated", "0.17.0", None, |cfg| { + // cfg.tool_config(CargoToolConfig { + // bin: Some("cargo-outdated".into()), + // ..Default::default() + // }); + // }); + // } // mod git { // use super::*; diff --git a/backends/cargo/tests/locate_test.rs b/backends/cargo/tests/locate_test.rs index 2d2269f2..4501638b 100644 --- a/backends/cargo/tests/locate_test.rs +++ b/backends/cargo/tests/locate_test.rs @@ -1,9 +1,9 @@ use proto_pdk_test_utils::*; use std::path::PathBuf; -fn locate_input(sandbox: &ProtoWasmSandbox) -> LocateExecutablesInput { +fn locate_input(sandbox: &ProtoWasmSandbox, plugin: &WasmTestWrapper) -> LocateExecutablesInput { LocateExecutablesInput { - install_dir: VirtualPath::Real(sandbox.path().into()), + install_dir: plugin.tool.to_virtual_path(sandbox.path()), ..Default::default() } } @@ -17,7 +17,9 @@ mod cargo_backend_locate { sandbox.create_file("bin/cargo-nextest", ""); let plugin = sandbox.create_plugin("cargo:cargo-nextest").await; - let output = plugin.locate_executables(locate_input(&sandbox)).await; + let output = plugin + .locate_executables(locate_input(&sandbox, &plugin)) + .await; let exe = output.exes.get("cargo-nextest").unwrap(); assert_eq!(exe.exe_path, Some(PathBuf::from("bin/cargo-nextest"))); @@ -33,7 +35,9 @@ mod cargo_backend_locate { sandbox.create_file("bin/subdir/inner", ""); let plugin = sandbox.create_plugin("cargo:cargo-nextest").await; - let output = plugin.locate_executables(locate_input(&sandbox)).await; + let output = plugin + .locate_executables(locate_input(&sandbox, &plugin)) + .await; assert!(output.exes.contains_key("cargo-nextest")); assert!(!output.exes.contains_key("subdir")); @@ -47,7 +51,9 @@ mod cargo_backend_locate { sandbox.create_file("bin/other", ""); let plugin = sandbox.create_plugin("cargo:eza").await; - let output = plugin.locate_executables(locate_input(&sandbox)).await; + let output = plugin + .locate_executables(locate_input(&sandbox, &plugin)) + .await; let eza = output.exes.get("eza").unwrap(); assert!(eza.primary); @@ -64,7 +70,9 @@ mod cargo_backend_locate { sandbox.create_file("bin/outdated", ""); let plugin = sandbox.create_plugin("cargo:cargo-outdated").await; - let output = plugin.locate_executables(locate_input(&sandbox)).await; + let output = plugin + .locate_executables(locate_input(&sandbox, &plugin)) + .await; let exe = output.exes.get("outdated").unwrap(); assert!(exe.primary); @@ -76,7 +84,9 @@ mod cargo_backend_locate { sandbox.create_file("bin/something-else", ""); let plugin = sandbox.create_plugin("cargo:cargo-nextest").await; - let output = plugin.locate_executables(locate_input(&sandbox)).await; + let output = plugin + .locate_executables(locate_input(&sandbox, &plugin)) + .await; assert_eq!(output.exes.values().filter(|cfg| cfg.primary).count(), 1); } @@ -87,7 +97,9 @@ mod cargo_backend_locate { sandbox.create_file("bin/cargo-nextest.exe", ""); let plugin = sandbox.create_plugin("cargo:cargo-nextest").await; - let output = plugin.locate_executables(locate_input(&sandbox)).await; + let output = plugin + .locate_executables(locate_input(&sandbox, &plugin)) + .await; // Map key has `.exe` stripped, but exe_path retains it. let exe = output.exes.get("cargo-nextest").unwrap(); diff --git a/backends/npm/CHANGELOG.md b/backends/npm/CHANGELOG.md index 0c6a9f29..4fe65b9b 100644 --- a/backends/npm/CHANGELOG.md +++ b/backends/npm/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## Unreleased + +#### 🚀 Updates + +- Updated to support proto v0.60 release. + ## 0.1.2 #### 🚀 Updates diff --git a/backends/npm/src/proto.rs b/backends/npm/src/proto.rs index 9b00c153..7cb0bbe5 100644 --- a/backends/npm/src/proto.rs +++ b/backends/npm/src/proto.rs @@ -33,10 +33,10 @@ pub fn register_tool(Json(input): Json) -> FnResult LocateExecutablesInput { +fn locate_input(sandbox: &ProtoWasmSandbox, plugin: &WasmTestWrapper) -> LocateExecutablesInput { LocateExecutablesInput { - install_dir: VirtualPath::Real(sandbox.path().into()), + install_dir: plugin.tool.to_virtual_path(sandbox.path()), ..Default::default() } } @@ -24,7 +24,9 @@ mod npm_backend_locate { ); let plugin = sandbox.create_plugin("npm:typescript").await; - let output = plugin.locate_executables(locate_input(&sandbox)).await; + let output = plugin + .locate_executables(locate_input(&sandbox, &plugin)) + .await; let tsc = output.exes.get("tsc").unwrap(); assert_eq!( @@ -52,7 +54,9 @@ mod npm_backend_locate { ); let plugin = sandbox.create_plugin("npm:prettier").await; - let output = plugin.locate_executables(locate_input(&sandbox)).await; + let output = plugin + .locate_executables(locate_input(&sandbox, &plugin)) + .await; let prettier = output.exes.get("prettier").unwrap(); assert_eq!( @@ -73,7 +77,9 @@ mod npm_backend_locate { ); let plugin = sandbox.create_plugin("npm:@moonrepo/cli").await; - let output = plugin.locate_executables(locate_input(&sandbox)).await; + let output = plugin + .locate_executables(locate_input(&sandbox, &plugin)) + .await; assert!(output.exes.contains_key("cli")); assert!(!output.exes.contains_key("@moonrepo/cli")); @@ -95,7 +101,9 @@ mod npm_backend_locate { ); let plugin = sandbox.create_plugin("npm:multi").await; - let output = plugin.locate_executables(locate_input(&sandbox)).await; + let output = plugin + .locate_executables(locate_input(&sandbox, &plugin)) + .await; for name in ["a", "b", "c"] { let cfg = output.exes.get(name).unwrap(); @@ -113,7 +121,9 @@ mod npm_backend_locate { ); let plugin = sandbox.create_plugin("npm:multi").await; - let output = plugin.locate_executables(locate_input(&sandbox)).await; + let output = plugin + .locate_executables(locate_input(&sandbox, &plugin)) + .await; for name in ["a", "b", "c", "d"] { let cfg = output.exes.get(name).unwrap(); @@ -131,7 +141,9 @@ mod npm_backend_locate { ); let plugin = sandbox.create_plugin("npm:typescript").await; - let output = plugin.locate_executables(locate_input(&sandbox)).await; + let output = plugin + .locate_executables(locate_input(&sandbox, &plugin)) + .await; let tsc = output.exes.get("tsc").unwrap(); assert_eq!(tsc.parent_exe_name, Some("node".into())); @@ -145,7 +157,9 @@ mod npm_backend_locate { sandbox.create_file("bin/extra", ""); let plugin = sandbox.create_plugin("npm:typescript").await; - let output = plugin.locate_executables(locate_input(&sandbox)).await; + let output = plugin + .locate_executables(locate_input(&sandbox, &plugin)) + .await; let typescript = output.exes.get("typescript").unwrap(); assert_eq!(typescript.exe_path, Some(PathBuf::from("bin/typescript"))); @@ -162,7 +176,9 @@ mod npm_backend_locate { sandbox.create_file("typescript", ""); let plugin = sandbox.create_plugin("npm:typescript").await; - let output = plugin.locate_executables(locate_input(&sandbox)).await; + let output = plugin + .locate_executables(locate_input(&sandbox, &plugin)) + .await; let typescript = output.exes.get("typescript").unwrap(); assert_eq!(typescript.exe_path, Some(PathBuf::from("typescript"))); @@ -177,7 +193,9 @@ mod npm_backend_locate { sandbox.create_file("typescript.ps1", ""); let plugin = sandbox.create_plugin("npm:typescript").await; - let output = plugin.locate_executables(locate_input(&sandbox)).await; + let output = plugin + .locate_executables(locate_input(&sandbox, &plugin)) + .await; assert!(output.exes.contains_key("typescript")); assert!(!output.exes.contains_key("typescript.cmd")); @@ -192,7 +210,9 @@ mod npm_backend_locate { sandbox.create_file("bin/bar", ""); let plugin = sandbox.create_plugin("npm:typescript").await; - let output = plugin.locate_executables(locate_input(&sandbox)).await; + let output = plugin + .locate_executables(locate_input(&sandbox, &plugin)) + .await; assert_eq!(output.exes.values().filter(|cfg| cfg.primary).count(), 1); } @@ -207,7 +227,9 @@ mod npm_backend_locate { sandbox.create_file("bin/.keep", ""); let plugin = sandbox.create_plugin("npm:typescript").await; - let output = plugin.locate_executables(locate_input(&sandbox)).await; + let output = plugin + .locate_executables(locate_input(&sandbox, &plugin)) + .await; assert!(output.exes_dirs.contains(&PathBuf::from("bin"))); } @@ -221,7 +243,9 @@ mod npm_backend_locate { ); let plugin = sandbox.create_plugin("npm:typescript").await; - let output = plugin.locate_executables(locate_input(&sandbox)).await; + let output = plugin + .locate_executables(locate_input(&sandbox, &plugin)) + .await; assert!(output.exes_dirs.contains(&PathBuf::from("."))); } @@ -243,7 +267,9 @@ mod npm_backend_locate { cfg.backend_config(NpmBackendConfig { bun: true }); }) .await; - let output = plugin.locate_executables(locate_input(&sandbox)).await; + let output = plugin + .locate_executables(locate_input(&sandbox, &plugin)) + .await; let tsc = output.exes.get("tsc").unwrap(); assert_eq!( @@ -266,7 +292,9 @@ mod npm_backend_locate { cfg.backend_config(NpmBackendConfig { bun: true }); }) .await; - let output = plugin.locate_executables(locate_input(&sandbox)).await; + let output = plugin + .locate_executables(locate_input(&sandbox, &plugin)) + .await; let cli = output.exes.get("cli").unwrap(); assert_eq!( diff --git a/extensions/migrate-nx/src/nx_migrator.rs b/extensions/migrate-nx/src/nx_migrator.rs index 071cf7ff..d8aa1ee4 100644 --- a/extensions/migrate-nx/src/nx_migrator.rs +++ b/extensions/migrate-nx/src/nx_migrator.rs @@ -213,7 +213,7 @@ impl NxMigrator { args: Some(PartialTaskArgs::List(migrate_options_to_args( &config_options, ))), - ..PartialTaskConfig::default() + ..Default::default() }, ); } @@ -400,7 +400,7 @@ fn inject_args_into_task(nx_target: &NxTargetOptions, config: &mut PartialTaskCo fn migrate_noop_task(nx_target: &NxTargetOptions) -> AnyResult { let mut config = PartialTaskConfig { command: Some(PartialTaskArgs::String("noop".into())), - ..PartialTaskConfig::default() + ..Default::default() }; inject_args_into_task(nx_target, &mut config); @@ -412,7 +412,7 @@ fn migrate_noop_task(nx_target: &NxTargetOptions) -> AnyResult AnyResult { let mut config = PartialTaskConfig { toolchains: Some(OneOrMany::One(Id::raw("system"))), - ..PartialTaskConfig::default() + ..Default::default() }; // https://nx.dev/nx-api/nx/executors/run-commands#options @@ -510,7 +510,7 @@ fn migrate_task( } else { format!("{package} {target}") })), - ..PartialTaskConfig::default() + ..Default::default() } } } else { diff --git a/toolchains/python/tests/tier2_test.rs b/toolchains/python/tests/tier2_test.rs index 0ab1aed8..3d0bc634 100644 --- a/toolchains/python/tests/tier2_test.rs +++ b/toolchains/python/tests/tier2_test.rs @@ -968,9 +968,9 @@ dependencies = ["internal-lib"] ], stream: true, cwd: Some(plugin.plugin.to_virtual_path(sandbox.path())), - ..ExecCommandInput::default() + ..Default::default() }, - ..ExecCommand::default() + ..Default::default() }; assert_eq!(actual, expected); @@ -1008,9 +1008,9 @@ dependencies = ["internal-lib"] args: vec!["pip".into(), "install".into()], cwd: Some(plugin.plugin.to_virtual_path(sandbox.path())), stream: true, - ..ExecCommandInput::default() + ..Default::default() }, - ..ExecCommand::default() + ..Default::default() }; assert_eq!(actual, expected); diff --git a/toolchains/typescript/tests/tsconfig_json_test.rs b/toolchains/typescript/tests/tsconfig_json_test.rs index 0aad8a7e..46e6988f 100644 --- a/toolchains/typescript/tests/tsconfig_json_test.rs +++ b/toolchains/typescript/tests/tsconfig_json_test.rs @@ -46,9 +46,9 @@ mod tsconfig_json { "es2020.symbol.wellknown", "es2021.weakref", ]), - ..CompilerOptions::default() + ..Default::default() }), - ..TsConfigJson::default() + ..Default::default() }; let expected = serde_json::json!({ @@ -113,9 +113,9 @@ mod tsconfig_json { module: Some(ModuleField::Es2015), module_resolution: Some(ModuleResolutionField::Classic), target: Some(TargetField::EsNext), - ..CompilerOptions::default() + ..Default::default() }), - ..TsConfigJson::default() + ..Default::default() }; let actual_typed: TsConfigJson = serde_json::from_value(actual).unwrap(); @@ -205,7 +205,7 @@ mod tsconfig_json { path: "../sibling".into(), prepend: None, }]), - ..TsConfigJson::default() + ..Default::default() }, path: VirtualPath::Real(PathBuf::from("/base/tsconfig.json")), ..Default::default() @@ -232,7 +232,7 @@ mod tsconfig_json { fn includes_custom_config_name() { let mut tsc = TsConfigJsonContainer { data: TsConfigJson { - ..TsConfigJson::default() + ..Default::default() }, path: VirtualPath::Real(PathBuf::from("/base/tsconfig.json")), ..Default::default() @@ -262,7 +262,7 @@ mod tsconfig_json { fn forces_forward_slash() { let mut tsc = TsConfigJsonContainer { data: TsConfigJson { - ..TsConfigJson::default() + ..Default::default() }, path: VirtualPath::Real(PathBuf::from("C:\\base\\dir\\tsconfig.json")), ..Default::default() @@ -295,7 +295,7 @@ mod tsconfig_json { path: "../sister".into(), prepend: None, }]), - ..TsConfigJson::default() + ..Default::default() }, path: VirtualPath::Real(PathBuf::from("/base/tsconfig.json")), ..Default::default() @@ -336,7 +336,7 @@ mod tsconfig_json { path: "../stale".into(), prepend: None, }]), - ..TsConfigJson::default() + ..Default::default() }, path: VirtualPath::Real(PathBuf::from("/base/tsconfig.json")), ..Default::default() @@ -367,7 +367,7 @@ mod tsconfig_json { path: "../stale".into(), prepend: None, }]), - ..TsConfigJson::default() + ..Default::default() }, path: VirtualPath::Real(PathBuf::from("/base/tsconfig.json")), ..Default::default() @@ -422,9 +422,9 @@ mod tsconfig_json { data: TsConfigJson { compiler_options: Some(CompilerOptions { out_dir: Some("./old".into()), - ..CompilerOptions::default() + ..Default::default() }), - ..TsConfigJson::default() + ..Default::default() }, ..Default::default() }; diff --git a/tools/bun/CHANGELOG.md b/tools/bun/CHANGELOG.md index ef96662d..1adb2193 100644 --- a/tools/bun/CHANGELOG.md +++ b/tools/bun/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## Unreleased + +#### 🚀 Updates + +- Updated to support proto v0.60 release. + ## 0.16.9 #### 🚀 Updates diff --git a/tools/bun/src/proto.rs b/tools/bun/src/proto.rs index b40ea7df..db96746b 100644 --- a/tools/bun/src/proto.rs +++ b/tools/bun/src/proto.rs @@ -25,10 +25,10 @@ pub fn register_tool(Json(_): Json) -> FnResult [HostArch::X64, HostArch::Arm64], @@ -207,7 +207,7 @@ pub fn download_prebuilt( let mut avx2_suffix = ""; - if env.arch == HostArch::X64 && env.os.is_linux() && command_exists(&env, "grep") { + if env.arch == HostArch::X64 && env.os.is_linux() && command_exists(env, "grep") { let output = exec_captured("grep", ["avx2", "/proc/cpuinfo"])?; if output.exit_code != 0 { @@ -245,7 +245,7 @@ pub fn download_prebuilt( .replace("{file}", "SHASUMS256.txt"), ) }, - ..DownloadPrebuiltOutput::default() + ..Default::default() })) } @@ -265,7 +265,7 @@ pub fn locate_executables( // so execute `bun x` instead (notice the space). shim_before_args: Some(StringOrVec::String("x".into())), - ..ExecutableConfig::default() + ..Default::default() }; Ok(Json(LocateExecutablesOutput { @@ -277,6 +277,6 @@ pub fn locate_executables( ("bunx".into(), bunx), ]), globals_lookup_dirs: vec!["$HOME/.bun/bin".into()], - ..LocateExecutablesOutput::default() + ..Default::default() })) } diff --git a/tools/bun/tests/versions_test.rs b/tools/bun/tests/versions_test.rs index da48220b..e2b6800f 100644 --- a/tools/bun/tests/versions_test.rs +++ b/tools/bun/tests/versions_test.rs @@ -179,7 +179,7 @@ mod bun_tool { assert_eq!( plugin .pin_version(PinVersionInput { - dir: VirtualPath::Real(sandbox.path().into()), + dir: plugin.tool.to_virtual_path(sandbox.path()), version: UnresolvedVersionSpec::parse(">=1").unwrap(), ..Default::default() }) @@ -204,7 +204,7 @@ mod bun_tool { assert_eq!( plugin .pin_version(PinVersionInput { - dir: VirtualPath::Real(sandbox.path().into()), + dir: plugin.tool.to_virtual_path(sandbox.path()), version: UnresolvedVersionSpec::parse(">=1").unwrap(), ..Default::default() }) @@ -243,7 +243,7 @@ mod bun_tool { assert_eq!( plugin .pin_version(PinVersionInput { - dir: VirtualPath::Real(sandbox.path().into()), + dir: plugin.tool.to_virtual_path(sandbox.path()), version: UnresolvedVersionSpec::parse(">=1").unwrap(), ..Default::default() }) @@ -282,7 +282,7 @@ mod bun_tool { assert_eq!( plugin .pin_version(PinVersionInput { - dir: VirtualPath::Real(sandbox.path().into()), + dir: plugin.tool.to_virtual_path(sandbox.path()), version: UnresolvedVersionSpec::parse(">=1").unwrap(), ..Default::default() }) @@ -313,7 +313,7 @@ mod bun_tool { assert_eq!( plugin .unpin_version(UnpinVersionInput { - dir: VirtualPath::Real(sandbox.path().into()), + dir: plugin.tool.to_virtual_path(sandbox.path()), ..Default::default() }) .await, @@ -346,7 +346,7 @@ mod bun_tool { assert_eq!( plugin .unpin_version(UnpinVersionInput { - dir: VirtualPath::Real(sandbox.path().into()), + dir: plugin.tool.to_virtual_path(sandbox.path()), ..Default::default() }) .await, @@ -391,7 +391,7 @@ mod bun_tool { assert_eq!( plugin .unpin_version(UnpinVersionInput { - dir: VirtualPath::Real(sandbox.path().into()), + dir: plugin.tool.to_virtual_path(sandbox.path()), ..Default::default() }) .await, @@ -430,7 +430,7 @@ mod bun_tool { assert_eq!( plugin .unpin_version(UnpinVersionInput { - dir: VirtualPath::Real(sandbox.path().into()), + dir: plugin.tool.to_virtual_path(sandbox.path()), ..Default::default() }) .await, diff --git a/tools/deno/CHANGELOG.md b/tools/deno/CHANGELOG.md index 7e45cf72..adeab2c7 100644 --- a/tools/deno/CHANGELOG.md +++ b/tools/deno/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## Unreleased + +#### 🚀 Updates + +- Updated to support proto v0.60 release. + ## 0.15.10 #### 🚀 Updates diff --git a/tools/deno/src/proto.rs b/tools/deno/src/proto.rs index cb0d35bf..e030d679 100644 --- a/tools/deno/src/proto.rs +++ b/tools/deno/src/proto.rs @@ -19,10 +19,10 @@ pub fn register_tool(Json(_): Json) -> FnResult [HostArch::X64, HostArch::Arm64], HostOS::MacOS => [HostArch::X64, HostArch::Arm64], @@ -155,7 +155,7 @@ pub fn download_prebuilt( check_supported_os_and_arch( NAME, - &env, + env, permutations! [ HostOS::Linux => [HostArch::X64, HostArch::Arm64], HostOS::MacOS => [HostArch::X64, HostArch::Arm64], @@ -215,7 +215,7 @@ pub fn download_prebuilt( }, download_url, download_name: Some(filename), - ..DownloadPrebuiltOutput::default() + ..Default::default() })) } @@ -235,6 +235,6 @@ pub fn locate_executables( "$DENO_HOME/bin".into(), "$HOME/.deno/bin".into(), ], - ..LocateExecutablesOutput::default() + ..Default::default() })) } diff --git a/tools/example/src/proto.rs b/tools/example/src/proto.rs index 15280a48..8e305f50 100644 --- a/tools/example/src/proto.rs +++ b/tools/example/src/proto.rs @@ -10,6 +10,6 @@ pub fn register_tool(Json(_): Json) -> FnResult) -> FnResult [ HostArch::X64, HostArch::Arm64, HostArch::X86, HostArch::Arm, HostArch::S390x @@ -118,7 +118,7 @@ pub fn build_instructions( "./all.bash" } .into(), - cwd: Some("src".into()), + cwd: Some(input.install_dir.join("src")), ..Default::default() } })), @@ -138,7 +138,7 @@ pub fn download_prebuilt( check_supported_os_and_arch( NAME, - &env, + env, permutations! [ HostOS::Linux => [ HostArch::X64, HostArch::Arm64, HostArch::X86, HostArch::Arm, HostArch::S390x @@ -193,7 +193,7 @@ pub fn download_prebuilt( .replace("{version}", &version) .replace("{file}", &filename), download_name: Some(filename), - ..DownloadPrebuiltOutput::default() + ..Default::default() })) } @@ -220,7 +220,7 @@ pub fn locate_executables( "$GOPATH/bin".into(), "$HOME/go/bin".into(), ], - ..LocateExecutablesOutput::default() + ..Default::default() })) } diff --git a/tools/internal-schema/CHANGELOG.md b/tools/internal-schema/CHANGELOG.md index dd44bacc..0b9f7a12 100644 --- a/tools/internal-schema/CHANGELOG.md +++ b/tools/internal-schema/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## Unreleased + +#### 🚀 Updates + +- Updated to support proto v0.60 release. + ## 0.18.1 #### 🚀 Updates diff --git a/tools/internal-schema/src/proto.rs b/tools/internal-schema/src/proto.rs index c51bd11e..9e6aab70 100644 --- a/tools/internal-schema/src/proto.rs +++ b/tools/internal-schema/src/proto.rs @@ -42,7 +42,7 @@ pub fn register_tool(Json(_): Json) -> FnResult) -> FnResult PluginType::Language, SchemaType::VersionManager => PluginType::VersionManager, }, - minimum_proto_version: Some(Version::new(0, 59, 0)), + minimum_proto_version: Some(Version::new(0, 60, 0)), default_version: schema.metadata.default_version, plugin_version: match schema.metadata.plugin_version { Some(version) => Some(version), @@ -83,7 +83,7 @@ pub fn register_tool(Json(_): Json) -> FnResult FnResult> { let env = get_host_environment()?; let schema = get_schema()?; - let platform = get_platform(&schema, &env)?; + let platform = get_platform(&schema, env)?; if !platform.archs.is_empty() { check_supported_os_and_arch( &schema.name, - &env, + env, HashMap::from_iter([(env.os, platform.archs.clone())]), )?; } @@ -307,7 +307,7 @@ pub fn download_prebuilt( let is_canary = version.is_canary(); let download_file = - interpolate_tokens(&platform.download_file, version, &schema, platform, &env); + interpolate_tokens(&platform.download_file, version, &schema, platform, env); let download_url = interpolate_tokens( if is_canary { @@ -322,7 +322,7 @@ pub fn download_prebuilt( version, &schema, platform, - &env, + env, ) .replace("{download_file}", &download_file); @@ -331,7 +331,7 @@ pub fn download_prebuilt( version, &schema, platform, - &env, + env, ); let checksum_url = if is_canary { @@ -345,14 +345,14 @@ pub fn download_prebuilt( }; let checksum_url = checksum_url.map(|url| { - interpolate_tokens(url, version, &schema, platform, &env) + interpolate_tokens(url, version, &schema, platform, env) .replace("{checksum_file}", &checksum_file) }); let archive_prefix = platform .archive_prefix .as_ref() - .map(|prefix| interpolate_tokens(prefix, version, &schema, platform, &env)); + .map(|prefix| interpolate_tokens(prefix, version, &schema, platform, env)); Ok(Json(DownloadPrebuiltOutput { archive_prefix, @@ -361,7 +361,7 @@ pub fn download_prebuilt( checksum_public_key: schema.install.checksum_public_key, download_url, download_name: Some(download_file), - ..DownloadPrebuiltOutput::default() + ..Default::default() })) } @@ -387,7 +387,7 @@ pub fn locate_executables( ) -> FnResult> { let env = get_host_environment()?; let schema = get_schema()?; - let platform = get_platform(&schema, &env)?; + let platform = get_platform(&schema, env)?; let id = get_plugin_id()?; // On Windows, automatically add the `.exe` extension to all executables. @@ -422,7 +422,7 @@ pub fn locate_executables( &input.context.version, &schema, platform, - &env, + env, ) .into(), ); @@ -505,6 +505,5 @@ pub fn locate_executables( }, globals_lookup_dirs: schema.packages.globals_lookup_dirs, globals_prefix: schema.packages.globals_prefix, - ..Default::default() })) } diff --git a/tools/java/CHANGELOG.md b/tools/java/CHANGELOG.md index 336bacef..fd3a28c5 100644 --- a/tools/java/CHANGELOG.md +++ b/tools/java/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## Unreleased + +#### 🚀 Updates + +- Updated to support proto v0.60 release. + ## 0.1.0 #### 🚀 Updates diff --git a/tools/java/src/foojay.rs b/tools/java/src/foojay.rs index a9468e55..399cc68f 100644 --- a/tools/java/src/foojay.rs +++ b/tools/java/src/foojay.rs @@ -239,7 +239,7 @@ mod tests { HostEnvironment { os, libc, - ..HostEnvironment::default() + ..Default::default() } } @@ -249,7 +249,7 @@ mod tests { distribution: Some(Distribution::default()), lib_c_type, operating_system: "linux".into(), - ..FoojayPackage::default() + ..Default::default() } } diff --git a/tools/java/src/java.rs b/tools/java/src/java.rs index f9d9f130..58b37322 100644 --- a/tools/java/src/java.rs +++ b/tools/java/src/java.rs @@ -2,7 +2,7 @@ use crate::{ config::{Distribution, PackageType}, version::to_java_version, }; -use proto_pdk::{AnyResult, UnresolvedVersionSpec, VersionSpec}; +use proto_pdk_api::{AnyResult, UnresolvedVersionSpec, VersionSpec}; #[derive(Default)] pub struct JavaContext { diff --git a/tools/java/src/lib.rs b/tools/java/src/lib.rs index b96e1fa2..d5d567ee 100644 --- a/tools/java/src/lib.rs +++ b/tools/java/src/lib.rs @@ -1,4 +1,6 @@ -pub mod config; +#[cfg(feature = "wasm")] +mod config; +#[cfg(feature = "wasm")] mod foojay; mod java; #[cfg(feature = "wasm")] diff --git a/tools/java/src/proto.rs b/tools/java/src/proto.rs index 68472758..c955f775 100644 --- a/tools/java/src/proto.rs +++ b/tools/java/src/proto.rs @@ -25,7 +25,7 @@ pub fn register_tool(Json(input): Json) -> FnResult) -> FnResult package, None => { return Err(plugin_err!( @@ -301,8 +301,8 @@ pub fn activate_environment( let mut output = ActivateEnvironmentOutput::default(); let home_dir = get_home_dir(&input.context.tool_dir); - if let Some(home) = home_dir.real_path_string() { - output.env.insert("JAVA_HOME".into(), home); + if let Some(home) = home_dir.to_real_path()? { + output.env.insert("JAVA_HOME".into(), home.to_string()); } Ok(Json(output)) diff --git a/tools/java/tests/download_test.rs b/tools/java/tests/download_test.rs index 86d9344e..c27c845b 100644 --- a/tools/java/tests/download_test.rs +++ b/tools/java/tests/download_test.rs @@ -439,8 +439,9 @@ mod java_tool { .await; let mut input = create_locate_input("temurin-21.0.11+10"); - input.context.tool_dir = - VirtualPath::Real(sandbox.path().join(".proto/tools/jdk/21.0.11")); + input.context.tool_dir = plugin + .tool + .to_virtual_path(sandbox.path().join(".proto/tools/jdk/21.0.11")); let output = plugin.locate_executables(input).await; diff --git a/tools/java/tests/metadata_test.rs b/tools/java/tests/metadata_test.rs index 32bacd2b..0ff07cd7 100644 --- a/tools/java/tests/metadata_test.rs +++ b/tools/java/tests/metadata_test.rs @@ -15,7 +15,6 @@ mod java_tool { .await; assert_eq!(metadata.name, "Java"); - assert_eq!(metadata.minimum_proto_version, Some(Version::new(0, 59, 0))); assert!(matches!(metadata.unstable, Switch::Toggle(true))); } diff --git a/tools/moon/CHANGELOG.md b/tools/moon/CHANGELOG.md index 11ed164c..b2eb5405 100644 --- a/tools/moon/CHANGELOG.md +++ b/tools/moon/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## Unreleased + +#### 🚀 Updates + +- Updated to support proto v0.60 release. + ## 0.4.2 #### 🚀 Updates diff --git a/tools/moon/src/proto.rs b/tools/moon/src/proto.rs index 30399ab7..58e6d19f 100644 --- a/tools/moon/src/proto.rs +++ b/tools/moon/src/proto.rs @@ -14,10 +14,10 @@ pub fn register_tool(Json(_): Json) -> FnResult [HostArch::X64, HostArch::Arm64], HostOS::MacOS => [HostArch::X64, HostArch::Arm64], @@ -87,7 +87,7 @@ pub fn download_prebuilt( check_supported_os_and_arch( "moon", - &env, + env, permutations! [ HostOS::Linux => [HostArch::X64, HostArch::Arm64], HostOS::MacOS => [HostArch::X64, HostArch::Arm64], diff --git a/tools/node-depman/CHANGELOG.md b/tools/node-depman/CHANGELOG.md index 8d02db95..01afba33 100644 --- a/tools/node-depman/CHANGELOG.md +++ b/tools/node-depman/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## Unreleased + +#### 🚀 Updates + +- Updated to support proto v0.60 release. + ## 0.19.0 #### 🚀 Updates diff --git a/tools/node-depman/src/proto.rs b/tools/node-depman/src/proto.rs index dfcd3f66..96576b54 100644 --- a/tools/node-depman/src/proto.rs +++ b/tools/node-depman/src/proto.rs @@ -21,7 +21,6 @@ const CMD_SHIM_TEMPLATE: &str = include_str!("../templates/cmd-shim.cmd"); extern "ExtismHost" { fn exec_command(input: Json) -> Json; fn get_env_var(key: &str) -> String; - fn to_virtual_path(input: String) -> String; } #[plugin_fn] @@ -40,10 +39,10 @@ pub fn register_tool(Json(_): Json) -> FnResult VirtualPath { - VirtualPath::Virtual { - path: PathBuf::from("/proto/tools/node/globals/bin"), - virtual_prefix: PathBuf::from("/proto"), - real_prefix: PathBuf::from("/.proto"), - } + VirtualPath::new("/proto/tools/node/globals/bin") } mod npm { @@ -73,7 +68,7 @@ mod node_depman_tool { let result = plugin .activate_environment(ActivateEnvironmentInput { globals_dir: Some(create_globals_dir()), - ..ActivateEnvironmentInput::default() + ..Default::default() }) .await; @@ -81,11 +76,15 @@ mod node_depman_tool { result.env, HashMap::from_iter([( "PREFIX".into(), - if cfg!(windows) { - "/.proto/tools/node/globals/bin".into() - } else { - "/.proto/tools/node/globals".into() - } + sandbox + .path() + .join(if cfg!(windows) { + ".proto/tools/node/globals/bin" + } else { + ".proto/tools/node/globals" + }) + .to_string_lossy() + .to_string() )]) ); } @@ -141,7 +140,7 @@ mod node_depman_tool { let result = plugin .activate_environment(ActivateEnvironmentInput { globals_dir: Some(create_globals_dir()), - ..ActivateEnvironmentInput::default() + ..Default::default() }) .await; @@ -150,11 +149,19 @@ mod node_depman_tool { HashMap::from_iter([ ( "pnpm_config_global_dir".into(), - "/.proto/tools/node/globals".into() + sandbox + .path() + .join(".proto/tools/node/globals") + .to_string_lossy() + .to_string() ), ( "pnpm_config_global_bin_dir".into(), - "/.proto/tools/node/globals/bin".into() + sandbox + .path() + .join(".proto/tools/node/globals/bin") + .to_string_lossy() + .to_string() ) ]) ); @@ -211,13 +218,20 @@ mod node_depman_tool { let result = plugin .activate_environment(ActivateEnvironmentInput { globals_dir: Some(create_globals_dir()), - ..ActivateEnvironmentInput::default() + ..Default::default() }) .await; assert_eq!( result.env, - HashMap::from_iter([("PREFIX".into(), "/.proto/tools/node/globals".into())]) + HashMap::from_iter([( + "PREFIX".into(), + sandbox + .path() + .join(".proto/tools/node/globals") + .to_string_lossy() + .to_string() + )]) ); } } diff --git a/tools/node-depman/tests/download_test.rs b/tools/node-depman/tests/download_test.rs index b9c3c8ee..bf383a41 100644 --- a/tools/node-depman/tests/download_test.rs +++ b/tools/node-depman/tests/download_test.rs @@ -58,7 +58,7 @@ mod node_depman_tool { version: VersionSpec::parse("9.0.0").unwrap(), ..Default::default() }, - install_dir: VirtualPath::Real(sandbox.path().into()), + install_dir: plugin.tool.to_virtual_path(sandbox.path()), }) .await .exes @@ -116,7 +116,7 @@ mod node_depman_tool { .download_prebuilt(DownloadPrebuiltInput { context: PluginContext { version: VersionSpec::parse("9.0.0").unwrap(), - working_dir: VirtualPath::Real(sandbox.path().into()), + working_dir: plugin.tool.to_virtual_path(sandbox.path()), ..Default::default() }, ..Default::default() @@ -154,7 +154,7 @@ mod node_depman_tool { .download_prebuilt(DownloadPrebuiltInput { context: PluginContext { version: VersionSpec::parse("9.0.0").unwrap(), - working_dir: VirtualPath::Real(sandbox.path().into()), + working_dir: plugin.tool.to_virtual_path(sandbox.path()), ..Default::default() }, ..Default::default() @@ -221,7 +221,7 @@ mod node_depman_tool { version: VersionSpec::parse("8.0.0").unwrap(), ..Default::default() }, - install_dir: VirtualPath::Real(sandbox.path().into()), + install_dir: plugin.tool.to_virtual_path(sandbox.path()), }) .await .exes @@ -279,7 +279,7 @@ mod node_depman_tool { .download_prebuilt(DownloadPrebuiltInput { context: PluginContext { version: VersionSpec::parse("9.0.0").unwrap(), - working_dir: VirtualPath::Real(sandbox.path().into()), + working_dir: plugin.tool.to_virtual_path(sandbox.path()), ..Default::default() }, ..Default::default() @@ -317,7 +317,7 @@ mod node_depman_tool { .download_prebuilt(DownloadPrebuiltInput { context: PluginContext { version: VersionSpec::parse("9.0.0").unwrap(), - working_dir: VirtualPath::Real(sandbox.path().into()), + working_dir: plugin.tool.to_virtual_path(sandbox.path()), ..Default::default() }, ..Default::default() @@ -384,7 +384,7 @@ mod node_depman_tool { version: VersionSpec::parse("1.22.0").unwrap(), ..Default::default() }, - install_dir: VirtualPath::Real(sandbox.path().into()), + install_dir: plugin.tool.to_virtual_path(sandbox.path()), }) .await .exes @@ -483,7 +483,7 @@ mod node_depman_tool { version: VersionSpec::parse("3.6.1").unwrap(), ..Default::default() }, - install_dir: VirtualPath::Real(sandbox.path().into()), + install_dir: plugin.tool.to_virtual_path(sandbox.path()), }) .await .exes @@ -510,7 +510,7 @@ mod node_depman_tool { .download_prebuilt(DownloadPrebuiltInput { context: PluginContext { version: VersionSpec::parse("4.5.0").unwrap(), - working_dir: VirtualPath::Real(sandbox.path().into()), + working_dir: plugin.tool.to_virtual_path(sandbox.path()), ..Default::default() }, ..Default::default() @@ -558,7 +558,7 @@ npmRegistries: .download_prebuilt(DownloadPrebuiltInput { context: PluginContext { version: VersionSpec::parse("4.5.0").unwrap(), - working_dir: VirtualPath::Real(sandbox.path().into()), + working_dir: plugin.tool.to_virtual_path(sandbox.path()), ..Default::default() }, ..Default::default() @@ -818,15 +818,12 @@ npmRegistries: version: VersionSpec::parse("6.0.0-rc.19").unwrap(), ..Default::default() }, - install_dir: VirtualPath::Real(sandbox.path().into()), + install_dir: plugin.tool.to_virtual_path(sandbox.path()), }) .await .exes; - assert_eq!( - exes.get("yarn").unwrap().exe_path, - Some("yarn-bin".into()) - ); + assert_eq!(exes.get("yarn").unwrap().exe_path, Some("yarn-bin".into())); // The yarnpkg alias is not supported in v6 assert!(!exes.contains_key("yarnpkg")); @@ -850,7 +847,7 @@ npmRegistries: version: VersionSpec::parse("6.0.0-rc.19").unwrap(), ..Default::default() }, - install_dir: VirtualPath::Real(sandbox.path().into()), + install_dir: plugin.tool.to_virtual_path(sandbox.path()), }) .await .exes; diff --git a/tools/node-depman/tests/versions_test.rs b/tools/node-depman/tests/versions_test.rs index 8035a43e..67248e13 100644 --- a/tools/node-depman/tests/versions_test.rs +++ b/tools/node-depman/tests/versions_test.rs @@ -19,7 +19,9 @@ mod node_depman_tool { .parse_version_file(ParseVersionFileInput { content: r#"{ "volta": { "extends": "./a.json" } }"#.into(), file: "package.json".into(), - path: VirtualPath::Real(sandbox.path().join("package.json")), + path: plugin + .tool + .to_virtual_path(sandbox.path().join("package.json")), ..Default::default() }) .await, @@ -448,7 +450,7 @@ mod node_depman_tool { assert_eq!( plugin .pin_version(PinVersionInput { - dir: VirtualPath::Real(sandbox.path().into()), + dir: plugin.tool.to_virtual_path(sandbox.path()), version: UnresolvedVersionSpec::parse(">=10").unwrap(), ..Default::default() }) @@ -473,7 +475,7 @@ mod node_depman_tool { assert_eq!( plugin .pin_version(PinVersionInput { - dir: VirtualPath::Real(sandbox.path().into()), + dir: plugin.tool.to_virtual_path(sandbox.path()), version: UnresolvedVersionSpec::parse(">=10").unwrap(), ..Default::default() }) @@ -512,7 +514,7 @@ mod node_depman_tool { assert_eq!( plugin .pin_version(PinVersionInput { - dir: VirtualPath::Real(sandbox.path().into()), + dir: plugin.tool.to_virtual_path(sandbox.path()), version: UnresolvedVersionSpec::parse(">=10").unwrap(), ..Default::default() }) @@ -551,7 +553,7 @@ mod node_depman_tool { assert_eq!( plugin .pin_version(PinVersionInput { - dir: VirtualPath::Real(sandbox.path().into()), + dir: plugin.tool.to_virtual_path(sandbox.path()), version: UnresolvedVersionSpec::parse(">=10").unwrap(), ..Default::default() }) @@ -582,7 +584,7 @@ mod node_depman_tool { assert_eq!( plugin .unpin_version(UnpinVersionInput { - dir: VirtualPath::Real(sandbox.path().into()), + dir: plugin.tool.to_virtual_path(sandbox.path()), ..Default::default() }) .await, @@ -615,7 +617,7 @@ mod node_depman_tool { assert_eq!( plugin .unpin_version(UnpinVersionInput { - dir: VirtualPath::Real(sandbox.path().into()), + dir: plugin.tool.to_virtual_path(sandbox.path()), ..Default::default() }) .await, @@ -660,7 +662,7 @@ mod node_depman_tool { assert_eq!( plugin .unpin_version(UnpinVersionInput { - dir: VirtualPath::Real(sandbox.path().into()), + dir: plugin.tool.to_virtual_path(sandbox.path()), ..Default::default() }) .await, @@ -699,7 +701,7 @@ mod node_depman_tool { assert_eq!( plugin .unpin_version(UnpinVersionInput { - dir: VirtualPath::Real(sandbox.path().into()), + dir: plugin.tool.to_virtual_path(sandbox.path()), ..Default::default() }) .await, diff --git a/tools/node/CHANGELOG.md b/tools/node/CHANGELOG.md index 8803db2f..087c5bcb 100644 --- a/tools/node/CHANGELOG.md +++ b/tools/node/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## Unreleased + +#### 🚀 Updates + +- Updated to support proto v0.60 release. + ## 0.17.10 #### 🚀 Updates diff --git a/tools/node/src/proto.rs b/tools/node/src/proto.rs index 5b31294c..e0b4f298 100644 --- a/tools/node/src/proto.rs +++ b/tools/node/src/proto.rs @@ -24,9 +24,9 @@ pub fn register_tool(Json(_): Json) -> FnResult [HostArch::X64, HostArch::Arm64, HostArch::Arm, HostArch::Powerpc64, HostArch::S390x], HostOS::MacOS => [HostArch::X64, HostArch::Arm64], @@ -273,7 +273,7 @@ pub fn download_prebuilt( check_supported_os_and_arch( NAME, - &env, + env, permutations! [ HostOS::Linux => [HostArch::X64, HostArch::Arm64, HostArch::Arm, HostArch::Powerpc64, HostArch::S390x], HostOS::MacOS => [HostArch::X64, HostArch::Arm64], @@ -363,7 +363,7 @@ pub fn download_prebuilt( host.replace("{version}", &version.to_string()) .replace("{file}", "SHASUMS256.txt"), ), - ..DownloadPrebuiltOutput::default() + ..Default::default() })) } @@ -388,7 +388,7 @@ pub fn locate_executables( "bin".into() }], globals_lookup_dirs: vec!["$PROTO_HOME/tools/node/globals/bin".into()], - ..LocateExecutablesOutput::default() + ..Default::default() })) } diff --git a/tools/node/tests/versions_test.rs b/tools/node/tests/versions_test.rs index 6f235d98..4b62adfe 100644 --- a/tools/node/tests/versions_test.rs +++ b/tools/node/tests/versions_test.rs @@ -200,7 +200,7 @@ mod node_tool { assert_eq!( plugin .pin_version(PinVersionInput { - dir: VirtualPath::Real(sandbox.path().into()), + dir: plugin.tool.to_virtual_path(sandbox.path()), version: UnresolvedVersionSpec::parse(">=20").unwrap(), ..Default::default() }) @@ -225,7 +225,7 @@ mod node_tool { assert_eq!( plugin .pin_version(PinVersionInput { - dir: VirtualPath::Real(sandbox.path().into()), + dir: plugin.tool.to_virtual_path(sandbox.path()), version: UnresolvedVersionSpec::parse(">=20").unwrap(), ..Default::default() }) @@ -264,7 +264,7 @@ mod node_tool { assert_eq!( plugin .pin_version(PinVersionInput { - dir: VirtualPath::Real(sandbox.path().into()), + dir: plugin.tool.to_virtual_path(sandbox.path()), version: UnresolvedVersionSpec::parse(">=20").unwrap(), ..Default::default() }) @@ -303,7 +303,7 @@ mod node_tool { assert_eq!( plugin .pin_version(PinVersionInput { - dir: VirtualPath::Real(sandbox.path().into()), + dir: plugin.tool.to_virtual_path(sandbox.path()), version: UnresolvedVersionSpec::parse(">=20").unwrap(), ..Default::default() }) @@ -334,7 +334,7 @@ mod node_tool { assert_eq!( plugin .unpin_version(UnpinVersionInput { - dir: VirtualPath::Real(sandbox.path().into()), + dir: plugin.tool.to_virtual_path(sandbox.path()), ..Default::default() }) .await, @@ -367,7 +367,7 @@ mod node_tool { assert_eq!( plugin .unpin_version(UnpinVersionInput { - dir: VirtualPath::Real(sandbox.path().into()), + dir: plugin.tool.to_virtual_path(sandbox.path()), ..Default::default() }) .await, @@ -412,7 +412,7 @@ mod node_tool { assert_eq!( plugin .unpin_version(UnpinVersionInput { - dir: VirtualPath::Real(sandbox.path().into()), + dir: plugin.tool.to_virtual_path(sandbox.path()), ..Default::default() }) .await, @@ -451,7 +451,7 @@ mod node_tool { assert_eq!( plugin .unpin_version(UnpinVersionInput { - dir: VirtualPath::Real(sandbox.path().into()), + dir: plugin.tool.to_virtual_path(sandbox.path()), ..Default::default() }) .await, diff --git a/tools/proto/CHANGELOG.md b/tools/proto/CHANGELOG.md index d7518d9f..4fb24864 100644 --- a/tools/proto/CHANGELOG.md +++ b/tools/proto/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## Unreleased + +#### 🚀 Updates + +- Updated to support proto v0.60 release. + ## 0.5.7 #### 🚀 Updates diff --git a/tools/proto/src/proto.rs b/tools/proto/src/proto.rs index e1525ca0..e146af3a 100644 --- a/tools/proto/src/proto.rs +++ b/tools/proto/src/proto.rs @@ -15,10 +15,10 @@ pub fn register_tool(Json(_): Json) -> FnResult [HostArch::X64, HostArch::Arm64], HostOS::MacOS => [HostArch::X64, HostArch::Arm64], @@ -103,7 +103,7 @@ pub fn download_prebuilt( check_supported_os_and_arch( "proto", - &env, + env, permutations! [ HostOS::Linux => [HostArch::X64, HostArch::Arm64], HostOS::MacOS => [HostArch::X64, HostArch::Arm64], @@ -139,7 +139,7 @@ pub fn download_prebuilt( checksum_name: Some(checksum_file), download_url: format!("{base_url}/{download_file}"), download_name: Some(download_file), - ..DownloadPrebuiltOutput::default() + ..Default::default() })) } @@ -159,6 +159,6 @@ pub fn locate_executables( Ok(Json(LocateExecutablesOutput { exes: HashMap::from_iter([("proto".into(), primary), ("proto-shim".into(), secondary)]), - ..LocateExecutablesOutput::default() + ..Default::default() })) } diff --git a/tools/python-poetry/CHANGELOG.md b/tools/python-poetry/CHANGELOG.md index 23fcc16d..1c85e524 100644 --- a/tools/python-poetry/CHANGELOG.md +++ b/tools/python-poetry/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## Unreleased + +#### 🚀 Updates + +- Updated to support proto v0.60 release. + ## 0.1.8 #### 🚀 Updates diff --git a/tools/python-poetry/src/proto.rs b/tools/python-poetry/src/proto.rs index ca33c614..3c3d4e50 100644 --- a/tools/python-poetry/src/proto.rs +++ b/tools/python-poetry/src/proto.rs @@ -21,11 +21,11 @@ pub fn register_tool(Json(_): Json) -> FnResult) -> FnResult [HostArch::X64, HostArch::Arm64], HostOS::MacOS => [HostArch::X64, HostArch::Arm64], @@ -119,7 +119,7 @@ pub fn download_prebuilt( checksum_name: Some(checksum_file), download_url: format!("{base_url}/{download_file}"), download_name: Some(download_file), - ..DownloadPrebuiltOutput::default() + ..Default::default() })) } @@ -147,6 +147,6 @@ pub fn locate_executables( "$XDG_DATA_HOME/../bin".into(), "$HOME/.local/bin".into(), ], - ..LocateExecutablesOutput::default() + ..Default::default() })) } diff --git a/tools/python-uv/tests/download_test.rs b/tools/python-uv/tests/download_test.rs index 29aa449b..6d88a37c 100644 --- a/tools/python-uv/tests/download_test.rs +++ b/tools/python-uv/tests/download_test.rs @@ -31,7 +31,7 @@ mod python_uv_tool { checksum_url: Some("https://github.com/astral-sh/uv/releases/download/1.41.0/uv-aarch64-unknown-linux-gnu.tar.gz.sha256".into()), download_name: Some("uv-aarch64-unknown-linux-gnu.tar.gz".into()), download_url: "https://github.com/astral-sh/uv/releases/download/1.41.0/uv-aarch64-unknown-linux-gnu.tar.gz".into(), - ..DownloadPrebuiltOutput::default() + ..Default::default() } ); } @@ -62,7 +62,7 @@ mod python_uv_tool { checksum_url: Some("https://github.com/astral-sh/uv/releases/download/1.2.0/uv-x86_64-unknown-linux-gnu.tar.gz.sha256".into()), download_name: Some("uv-x86_64-unknown-linux-gnu.tar.gz".into()), download_url: "https://github.com/astral-sh/uv/releases/download/1.2.0/uv-x86_64-unknown-linux-gnu.tar.gz".into(), - ..DownloadPrebuiltOutput::default() + ..Default::default() } ); } @@ -92,7 +92,7 @@ mod python_uv_tool { checksum_url: Some("https://github.com/astral-sh/uv/releases/download/1.2.0/uv-aarch64-apple-darwin.tar.gz.sha256".into()), download_name: Some("uv-aarch64-apple-darwin.tar.gz".into()), download_url: "https://github.com/astral-sh/uv/releases/download/1.2.0/uv-aarch64-apple-darwin.tar.gz".into(), - ..DownloadPrebuiltOutput::default() + ..Default::default() } ); } @@ -123,7 +123,7 @@ mod python_uv_tool { checksum_url: Some("https://github.com/astral-sh/uv/releases/download/1.2.0/uv-x86_64-apple-darwin.tar.gz.sha256".into()), download_name: Some("uv-x86_64-apple-darwin.tar.gz".into()), download_url: "https://github.com/astral-sh/uv/releases/download/1.2.0/uv-x86_64-apple-darwin.tar.gz".into(), - ..DownloadPrebuiltOutput::default() + ..Default::default() } ); } @@ -180,7 +180,7 @@ mod python_uv_tool { checksum_url: Some("https://github.com/astral-sh/uv/releases/download/1.2.0/uv-x86_64-pc-windows-msvc.zip.sha256".into()), download_name: Some("uv-x86_64-pc-windows-msvc.zip".into()), download_url: "https://github.com/astral-sh/uv/releases/download/1.2.0/uv-x86_64-pc-windows-msvc.zip".into(), - ..DownloadPrebuiltOutput::default() + ..Default::default() } ); } diff --git a/tools/python/CHANGELOG.md b/tools/python/CHANGELOG.md index c8c8fed5..cda8e214 100644 --- a/tools/python/CHANGELOG.md +++ b/tools/python/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## Unreleased + +#### 🚀 Updates + +- Updated to support proto v0.60 release. + ## 0.14.8 #### 🚀 Updates diff --git a/tools/python/src/proto.rs b/tools/python/src/proto.rs index 90c5bf80..b90f5031 100644 --- a/tools/python/src/proto.rs +++ b/tools/python/src/proto.rs @@ -21,9 +21,9 @@ pub fn register_tool(Json(_): Json) -> FnResult>; - -#[derive(Debug, PartialEq)] -struct PrebuiltAsset { - filename: String, - url: String, -} - #[host_fn] extern "ExtismHost" { fn exec_command(input: Json) -> Json; @@ -23,11 +16,11 @@ pub fn register_tool(Json(_): Json) -> FnResult AnyResult> { - let version = version.to_string(); - - Ok(load_prebuilt_asset(env, &version)?.map(|asset| { - SourceLocation::Archive(ArchiveSource { - url: asset.url, - prefix: Some(format!("ruby-{version}")), - }) - })) -} - #[plugin_fn] pub fn download_prebuilt( Json(input): Json, ) -> FnResult> { let env = get_host_environment()?; - let version = input.context.version.to_string(); + let version = &input.context.version; - let Some(asset) = load_prebuilt_asset(&env, &version)? else { + let Some(asset) = load_prebuilt_asset(env, version)? else { return Err(plugin_err!( - "No pre-built available for Ruby {version} on {}-{}! Try building from source with --build.", + "No pre-built available for {version} on {}-{}! Try building from source with --build.", env.os, env.arch, )); }; - Ok(Json(create_download_output(asset, &version))) -} - -fn load_prebuilt_asset(env: &HostEnvironment, version: &str) -> AnyResult> { - let Some(platform) = get_prebuilt_platform(env) else { - return Ok(None); - }; - - let releases: PrebuiltReleases = fetch_json( - "https://raw.githubusercontent.com/moonrepo/plugins/master/tools/ruby/releases.json", - )?; - - Ok(select_prebuilt_asset(&releases, platform, version)) -} - -fn select_prebuilt_asset( - releases: &PrebuiltReleases, - platform: &str, - version: &str, -) -> Option { - let filename = releases.get(version)?.get(platform)?; - - Some(PrebuiltAsset { - filename: filename.to_owned(), - url: format!("https://github.com/jdx/ruby/releases/download/{version}/{filename}"), - }) -} - -fn create_download_output(asset: PrebuiltAsset, version: &str) -> DownloadPrebuiltOutput { - DownloadPrebuiltOutput { - archive_prefix: Some(format!("ruby-{version}")), - download_name: Some(asset.filename), - download_url: asset.url, - ..DownloadPrebuiltOutput::default() - } -} - -fn get_prebuilt_platform(env: &HostEnvironment) -> Option<&'static str> { - match (env.os, env.arch) { - (HostOS::Linux, HostArch::X64) => Some("x86_64_linux"), - (HostOS::Linux, HostArch::Arm64) => Some("arm64_linux"), - (HostOS::MacOS, HostArch::Arm64) => Some("macos"), - _ => None, - } + Ok(Json(create_download_output(asset, version))) } #[plugin_fn] @@ -245,91 +184,6 @@ pub fn locate_executables( ]), exes_dirs: vec!["bin".into()], globals_lookup_dirs: vec![], - ..LocateExecutablesOutput::default() + ..Default::default() })) } - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn maps_jdx_supported_platforms() { - for (os, arch, expected) in [ - (HostOS::Linux, HostArch::X64, Some("x86_64_linux")), - (HostOS::Linux, HostArch::Arm64, Some("arm64_linux")), - (HostOS::MacOS, HostArch::Arm64, Some("macos")), - (HostOS::MacOS, HostArch::X64, None), - (HostOS::Windows, HostArch::X64, None), - ] { - assert_eq!( - get_prebuilt_platform(&HostEnvironment { - os, - arch, - ..HostEnvironment::default() - }), - expected - ); - } - } - - #[test] - fn selects_matching_release_asset() { - let asset = select_prebuilt_asset( - &BTreeMap::from_iter([( - "3.4.9".into(), - BTreeMap::from_iter([( - "arm64_linux".into(), - "ruby-3.4.9.arm64_linux.tar.gz".into(), - )]), - )]), - "arm64_linux", - "3.4.9", - ); - - assert_eq!( - asset, - Some(PrebuiltAsset { - filename: "ruby-3.4.9.arm64_linux.tar.gz".into(), - url: "https://github.com/jdx/ruby/releases/download/3.4.9/ruby-3.4.9.arm64_linux.tar.gz".into(), - }) - ); - } - - #[test] - fn skips_release_without_matching_asset() { - let asset = select_prebuilt_asset( - &BTreeMap::from_iter([("3.4.9".into(), BTreeMap::new())]), - "macos", - "3.4.9", - ); - - assert_eq!(asset, None); - } - - #[test] - fn skips_missing_release() { - let asset = select_prebuilt_asset(&BTreeMap::new(), "macos", "3.1.0"); - - assert_eq!(asset, None); - } - - #[test] - fn creates_download_output() { - assert_eq!( - create_download_output( - PrebuiltAsset { - filename: "ruby-3.4.9.macos.tar.gz".into(), - url: "https://example.com/ruby-3.4.9.macos.tar.gz".into(), - }, - "3.4.9", - ), - DownloadPrebuiltOutput { - archive_prefix: Some("ruby-3.4.9".into()), - download_name: Some("ruby-3.4.9.macos.tar.gz".into()), - download_url: "https://example.com/ruby-3.4.9.macos.tar.gz".into(), - ..DownloadPrebuiltOutput::default() - } - ); - } -} diff --git a/tools/ruby/src/releases.rs b/tools/ruby/src/releases.rs new file mode 100644 index 00000000..eb328eaa --- /dev/null +++ b/tools/ruby/src/releases.rs @@ -0,0 +1,148 @@ +use proto_pdk::*; +use std::collections::BTreeMap; + +pub type PrebuiltReleases = BTreeMap>; + +#[derive(Debug, PartialEq)] +pub struct PrebuiltAsset { + pub filename: String, + pub url: String, +} + +pub fn load_prebuilt_asset( + env: &HostEnvironment, + version: &VersionSpec, +) -> AnyResult> { + let Some(platform) = get_prebuilt_platform(env) else { + return Ok(None); + }; + + let releases: PrebuiltReleases = fetch_json( + "https://raw.githubusercontent.com/moonrepo/plugins/master/tools/ruby/releases.json", + )?; + + Ok(select_prebuilt_asset(&releases, platform, version)) +} + +pub fn select_prebuilt_asset( + releases: &PrebuiltReleases, + platform: &str, + version: &VersionSpec, +) -> Option { + let filename = releases.get(&version.to_string())?.get(platform)?; + + Some(PrebuiltAsset { + filename: filename.to_owned(), + url: format!("https://github.com/jdx/ruby/releases/download/{version}/{filename}"), + }) +} + +pub fn create_download_output( + asset: PrebuiltAsset, + version: &VersionSpec, +) -> DownloadPrebuiltOutput { + DownloadPrebuiltOutput { + archive_prefix: Some(format!("ruby-{version}")), + download_name: Some(asset.filename), + download_url: asset.url, + ..Default::default() + } +} + +pub fn get_prebuilt_platform(env: &HostEnvironment) -> Option<&'static str> { + match (env.os, env.arch) { + (HostOS::Linux, HostArch::X64) => Some("x86_64_linux"), + (HostOS::Linux, HostArch::Arm64) => Some("arm64_linux"), + (HostOS::MacOS, HostArch::Arm64) => Some("macos"), + _ => None, + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn maps_jdx_supported_platforms() { + for (os, arch, expected) in [ + (HostOS::Linux, HostArch::X64, Some("x86_64_linux")), + (HostOS::Linux, HostArch::Arm64, Some("arm64_linux")), + (HostOS::MacOS, HostArch::Arm64, Some("macos")), + (HostOS::MacOS, HostArch::X64, None), + (HostOS::Windows, HostArch::X64, None), + ] { + assert_eq!( + get_prebuilt_platform(&HostEnvironment { + os, + arch, + ..Default::default() + }), + expected + ); + } + } + + #[test] + fn selects_matching_release_asset() { + let asset = select_prebuilt_asset( + &BTreeMap::from_iter([( + "3.4.9".into(), + BTreeMap::from_iter([( + "arm64_linux".into(), + "ruby-3.4.9.arm64_linux.tar.gz".into(), + )]), + )]), + "arm64_linux", + &VersionSpec::parse("3.4.9").unwrap(), + ); + + assert_eq!( + asset, + Some(PrebuiltAsset { + filename: "ruby-3.4.9.arm64_linux.tar.gz".into(), + url: "https://github.com/jdx/ruby/releases/download/3.4.9/ruby-3.4.9.arm64_linux.tar.gz".into(), + }) + ); + } + + #[test] + fn skips_release_without_matching_asset() { + let asset = select_prebuilt_asset( + &BTreeMap::from_iter([("3.4.9".into(), BTreeMap::new())]), + "macos", + &VersionSpec::parse("3.4.9").unwrap(), + ); + + assert_eq!(asset, None); + } + + #[test] + fn skips_missing_release() { + let asset = select_prebuilt_asset( + &BTreeMap::new(), + "macos", + &VersionSpec::parse("3.1.0").unwrap(), + ); + + assert_eq!(asset, None); + } + + #[test] + fn creates_download_output() { + assert_eq!( + create_download_output( + PrebuiltAsset { + filename: "ruby-3.4.9.macos.tar.gz".into(), + url: "https://example.com/ruby-3.4.9.macos.tar.gz".into(), + }, + &VersionSpec::parse("3.4.9").unwrap(), + ), + DownloadPrebuiltOutput { + archive_prefix: Some("ruby-3.4.9".into()), + download_name: Some("ruby-3.4.9.macos.tar.gz".into()), + download_url: "https://example.com/ruby-3.4.9.macos.tar.gz".into(), + ..Default::default() + } + ); + } +} diff --git a/tools/rust/CHANGELOG.md b/tools/rust/CHANGELOG.md index c3065077..3666a5b7 100644 --- a/tools/rust/CHANGELOG.md +++ b/tools/rust/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## Unreleased + +#### 🚀 Updates + +- Updated to support proto v0.60 release. + ## 0.13.9 #### 🚀 Updates diff --git a/tools/rust/src/helpers.rs b/tools/rust/src/helpers.rs index 969e128e..155facae 100644 --- a/tools/rust/src/helpers.rs +++ b/tools/rust/src/helpers.rs @@ -4,16 +4,17 @@ use proto_pdk::*; #[host_fn] extern "ExtismHost" { fn get_env_var(name: String) -> String; - fn to_virtual_path(input: String) -> String; } fn get_home_env(key: &str) -> Result, Error> { match get_host_env_var(key)? { - Some(value) => Ok(if value.is_empty() { - None - } else { - into_virtual_path(value).ok() - }), + Some(value) => { + if value.is_empty() { + Ok(None) + } else { + VirtualPath::create(value).map(Some) + } + } None => Ok(None), } } diff --git a/tools/rust/src/proto.rs b/tools/rust/src/proto.rs index e6fabaed..910ba72a 100644 --- a/tools/rust/src/proto.rs +++ b/tools/rust/src/proto.rs @@ -29,17 +29,17 @@ pub fn register_tool(Json(_): Json) -> FnResultrustup"); let is_windows = env.os.is_windows(); @@ -134,17 +134,20 @@ pub fn native_install( } exec(ExecCommandInput { - command: script_path.real_path_string().unwrap(), + command: script_path + .to_real_path()? + .expect("Invalid script path!") + .to_string(), args: vec!["--default-toolchain".into(), "none".into(), "-y".into()], set_executable: true, stream: true, - ..ExecCommandInput::default() + ..Default::default() })?; // Update PATH explicitly, since we can't "reload the shell" // on the host side. This is good enough since it's deterministic. add_host_paths([ - get_cargo_home(&env)?.join("bin").to_string(), + get_cargo_home(env)?.join("bin").to_string(), "$HOME/.cargo/bin".to_string(), ])?; } @@ -152,7 +155,7 @@ pub fn native_install( let version = &input.context.version; let channel = get_channel_from_version(version); - let triple = format!("{}-{}", channel, get_target_triple(&env, NAME)?); + let triple = format!("{}-{}", channel, get_target_triple(env, NAME)?); debug!("Installing target {} with rustup", triple); @@ -186,7 +189,7 @@ pub fn native_install( // Always mark as installed so that binaries can be located! Ok(Json(NativeInstallOutput { installed: true, - ..NativeInstallOutput::default() + ..Default::default() })) } @@ -196,13 +199,13 @@ pub fn native_uninstall( ) -> FnResult> { let env = get_host_environment()?; let channel = get_channel_from_version(&input.context.version); - let triple = format!("{}-{}", channel, get_target_triple(&env, NAME)?); + let triple = format!("{}-{}", channel, get_target_triple(env, NAME)?); exec_streamed("rustup", ["toolchain", "uninstall", &triple])?; Ok(Json(NativeUninstallOutput { uninstalled: true, - ..NativeUninstallOutput::default() + ..Default::default() })) } @@ -228,15 +231,14 @@ pub fn locate_executables( "$HOME/.cargo/bin".into(), ], globals_prefix: Some("cargo-".into()), - ..LocateExecutablesOutput::default() })) } #[plugin_fn] pub fn sync_manifest(Json(_): Json) -> FnResult> { let env = get_host_environment()?; - let triple = get_target_triple(&env, NAME)?; - let toolchain_dir = get_toolchain_dir(&env)?; + let triple = get_target_triple(env, NAME)?; + let toolchain_dir = get_toolchain_dir(env)?; let mut output = SyncManifestOutput::default(); let mut versions = vec![]; From 817cfde1b8d1504c26583ba32783420c82236074 Mon Sep 17 00:00:00 2001 From: Miles Johnson Date: Mon, 3 Aug 2026 14:12:19 -0700 Subject: [PATCH 05/78] chore: Release --- Cargo.lock | 2 +- backends/asdf/CHANGELOG.md | 2 +- backends/asdf/Cargo.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3fd67558..340251e4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -82,7 +82,7 @@ checksum = "d3fb67a6e08acf24fdeccbac2cb6ac4305825bd1f117462e0e6f2f193345ad56" [[package]] name = "asdf_backend" -version = "0.3.4" +version = "0.3.5" dependencies = [ "backend_common", "extism-pdk", diff --git a/backends/asdf/CHANGELOG.md b/backends/asdf/CHANGELOG.md index 230b2209..0fb90150 100644 --- a/backends/asdf/CHANGELOG.md +++ b/backends/asdf/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## Unreleased +## 0.3.5 #### 🚀 Updates diff --git a/backends/asdf/Cargo.toml b/backends/asdf/Cargo.toml index 6c7030f2..d260e1e4 100644 --- a/backends/asdf/Cargo.toml +++ b/backends/asdf/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "asdf_backend" -version = "0.3.4" +version = "0.3.5" edition = "2024" description = "asdf backend WASM plugin for proto, for installing tools via asdf plugins." authors = ["Miles Johnson"] From 507697a2494086bda38652cc9790d5a20543c85b Mon Sep 17 00:00:00 2001 From: Miles Johnson Date: Mon, 3 Aug 2026 14:12:32 -0700 Subject: [PATCH 06/78] chore: Release --- Cargo.lock | 2 +- backends/cargo/CHANGELOG.md | 2 +- backends/cargo/Cargo.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 340251e4..77227b8f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -450,7 +450,7 @@ dependencies = [ [[package]] name = "cargo_backend" -version = "0.1.1" +version = "0.1.2" dependencies = [ "backend_common", "extism-pdk", diff --git a/backends/cargo/CHANGELOG.md b/backends/cargo/CHANGELOG.md index 5ae1dd99..b23dcf96 100644 --- a/backends/cargo/CHANGELOG.md +++ b/backends/cargo/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## Unreleased +## 0.1.2 #### 🚀 Updates diff --git a/backends/cargo/Cargo.toml b/backends/cargo/Cargo.toml index 92e31d71..fdf30a2c 100644 --- a/backends/cargo/Cargo.toml +++ b/backends/cargo/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cargo_backend" -version = "0.1.1" +version = "0.1.2" edition = "2024" description = "Cargo backend WASM plugin for proto, for installing CLIs from crates.io." authors = ["Miles Johnson"] From 977e873d4758780179a661166677cbbffe95ff17 Mon Sep 17 00:00:00 2001 From: Miles Johnson Date: Mon, 3 Aug 2026 14:12:45 -0700 Subject: [PATCH 07/78] chore: Release --- Cargo.lock | 2 +- backends/npm/CHANGELOG.md | 2 +- backends/npm/Cargo.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 77227b8f..a5678d31 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3432,7 +3432,7 @@ checksum = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be" [[package]] name = "npm_backend" -version = "0.1.2" +version = "0.1.3" dependencies = [ "backend_common", "extism-pdk", diff --git a/backends/npm/CHANGELOG.md b/backends/npm/CHANGELOG.md index 4fe65b9b..b11f34d8 100644 --- a/backends/npm/CHANGELOG.md +++ b/backends/npm/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## Unreleased +## 0.1.3 #### 🚀 Updates diff --git a/backends/npm/Cargo.toml b/backends/npm/Cargo.toml index 4b70279a..ec944026 100644 --- a/backends/npm/Cargo.toml +++ b/backends/npm/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "npm_backend" -version = "0.1.2" +version = "0.1.3" edition = "2024" description = "npm backend WASM plugin for proto, for installing CLIs from npmjs.com." authors = ["Miles Johnson"] From ced2b0ec8f626bae3357799cb6a49a4407f257bc Mon Sep 17 00:00:00 2001 From: Miles Johnson Date: Mon, 3 Aug 2026 14:27:29 -0700 Subject: [PATCH 08/78] chore: Release --- Cargo.lock | 2 +- tools/bun/CHANGELOG.md | 2 +- tools/bun/Cargo.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a5678d31..3862f342 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -266,7 +266,7 @@ dependencies = [ [[package]] name = "bun_tool" -version = "0.16.9" +version = "0.16.10" dependencies = [ "extism-pdk", "lang_javascript_common", diff --git a/tools/bun/CHANGELOG.md b/tools/bun/CHANGELOG.md index 1adb2193..9c81453b 100644 --- a/tools/bun/CHANGELOG.md +++ b/tools/bun/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## Unreleased +## 0.16.10 #### 🚀 Updates diff --git a/tools/bun/Cargo.toml b/tools/bun/Cargo.toml index 0a23a86c..a8754658 100644 --- a/tools/bun/Cargo.toml +++ b/tools/bun/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "bun_tool" -version = "0.16.9" +version = "0.16.10" edition = "2024" description = "Bun WASM plugin for proto." authors = ["Miles Johnson"] From 1c23a07c39be7afb6a2ec18c252199566bcb4817 Mon Sep 17 00:00:00 2001 From: Miles Johnson Date: Mon, 3 Aug 2026 14:27:42 -0700 Subject: [PATCH 09/78] chore: Release --- Cargo.lock | 2 +- tools/deno/CHANGELOG.md | 2 +- tools/deno/Cargo.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3862f342..4fb74263 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1200,7 +1200,7 @@ dependencies = [ [[package]] name = "deno_tool" -version = "0.15.10" +version = "0.15.11" dependencies = [ "extism-pdk", "proto_pdk", diff --git a/tools/deno/CHANGELOG.md b/tools/deno/CHANGELOG.md index adeab2c7..c6edb97e 100644 --- a/tools/deno/CHANGELOG.md +++ b/tools/deno/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## Unreleased +## 0.15.11 #### 🚀 Updates diff --git a/tools/deno/Cargo.toml b/tools/deno/Cargo.toml index 5f0ca526..1074aeb8 100644 --- a/tools/deno/Cargo.toml +++ b/tools/deno/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "deno_tool" -version = "0.15.10" +version = "0.15.11" edition = "2024" description = "Deno WASM plugin for proto." authors = ["Miles Johnson"] From f501ab9971c56534a89a2b2247c538db8351e50f Mon Sep 17 00:00:00 2001 From: Miles Johnson Date: Mon, 3 Aug 2026 14:28:42 -0700 Subject: [PATCH 10/78] docs: Add changelog. --- tools/example/CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tools/example/CHANGELOG.md b/tools/example/CHANGELOG.md index 3e5b1460..c79354b7 100644 --- a/tools/example/CHANGELOG.md +++ b/tools/example/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## Unreleased + +#### 🚀 Updates + +- Updated to support proto v0.60 release. + ## 0.0.2 #### 🚀 Updates From 56d5a702da25ea38eea4d433c4e281ce2eaac4b2 Mon Sep 17 00:00:00 2001 From: Miles Johnson Date: Mon, 3 Aug 2026 14:29:10 -0700 Subject: [PATCH 11/78] chore: Release --- Cargo.lock | 2 +- tools/example/CHANGELOG.md | 2 +- tools/example/Cargo.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4fb74263..a46f7d23 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1523,7 +1523,7 @@ dependencies = [ [[package]] name = "example_tool" -version = "0.0.2" +version = "0.0.3" dependencies = [ "extism-pdk", "proto_pdk", diff --git a/tools/example/CHANGELOG.md b/tools/example/CHANGELOG.md index c79354b7..37317f28 100644 --- a/tools/example/CHANGELOG.md +++ b/tools/example/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## Unreleased +## 0.0.3 #### 🚀 Updates diff --git a/tools/example/Cargo.toml b/tools/example/Cargo.toml index 61bfedbf..35ce675e 100644 --- a/tools/example/Cargo.toml +++ b/tools/example/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "example_tool" -version = "0.0.2" +version = "0.0.3" edition = "2024" description = "Example tool." authors = ["Miles Johnson"] From ec90910d63c8418adfab3388c12e6847b928cf1c Mon Sep 17 00:00:00 2001 From: Miles Johnson Date: Mon, 3 Aug 2026 14:29:23 -0700 Subject: [PATCH 12/78] chore: Release --- Cargo.lock | 2 +- tools/go/CHANGELOG.md | 2 +- tools/go/Cargo.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a46f7d23..c7eaedfc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1984,7 +1984,7 @@ dependencies = [ [[package]] name = "go_tool" -version = "0.16.7" +version = "0.16.8" dependencies = [ "extism-pdk", "proto_pdk", diff --git a/tools/go/CHANGELOG.md b/tools/go/CHANGELOG.md index 02f660b2..1fd7f39a 100644 --- a/tools/go/CHANGELOG.md +++ b/tools/go/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## Unreleased +## 0.16.8 #### 🚀 Updates diff --git a/tools/go/Cargo.toml b/tools/go/Cargo.toml index b80e2690..05554cd3 100644 --- a/tools/go/Cargo.toml +++ b/tools/go/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "go_tool" -version = "0.16.7" +version = "0.16.8" edition = "2024" description = "Go WASM plugin for proto." authors = ["Miles Johnson"] From dfed7abe008b0c896336ac1e32295a816ab0ec47 Mon Sep 17 00:00:00 2001 From: Miles Johnson Date: Mon, 3 Aug 2026 14:29:35 -0700 Subject: [PATCH 13/78] chore: Release --- Cargo.lock | 2 +- tools/internal-schema/CHANGELOG.md | 2 +- tools/internal-schema/Cargo.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c7eaedfc..595cd022 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5014,7 +5014,7 @@ dependencies = [ [[package]] name = "schema_tool" -version = "0.18.1" +version = "0.18.2" dependencies = [ "extism-pdk", "proto_pdk", diff --git a/tools/internal-schema/CHANGELOG.md b/tools/internal-schema/CHANGELOG.md index 0b9f7a12..f8ceaf02 100644 --- a/tools/internal-schema/CHANGELOG.md +++ b/tools/internal-schema/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## Unreleased +## 0.18.2 #### 🚀 Updates diff --git a/tools/internal-schema/Cargo.toml b/tools/internal-schema/Cargo.toml index 7b560bde..82f397e0 100644 --- a/tools/internal-schema/Cargo.toml +++ b/tools/internal-schema/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "schema_tool" -version = "0.18.1" +version = "0.18.2" edition = "2024" description = "Schema-based WASM plugin for proto, powering the TOML plugin pattern." authors = ["Miles Johnson"] From 7d0871f4ce28cab7d63e45b93fb5d8cf724f78ea Mon Sep 17 00:00:00 2001 From: Miles Johnson Date: Mon, 3 Aug 2026 14:29:47 -0700 Subject: [PATCH 14/78] chore: Release --- Cargo.lock | 2 +- tools/java/CHANGELOG.md | 2 +- tools/java/Cargo.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 595cd022..d2c9aecf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2592,7 +2592,7 @@ dependencies = [ [[package]] name = "java_tool" -version = "0.1.0" +version = "0.1.1" dependencies = [ "extism-pdk", "proto_pdk", diff --git a/tools/java/CHANGELOG.md b/tools/java/CHANGELOG.md index fd3a28c5..bc4b9b39 100644 --- a/tools/java/CHANGELOG.md +++ b/tools/java/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## Unreleased +## 0.1.1 #### 🚀 Updates diff --git a/tools/java/Cargo.toml b/tools/java/Cargo.toml index cbe3ef27..355bbce1 100644 --- a/tools/java/Cargo.toml +++ b/tools/java/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "java_tool" -version = "0.1.0" +version = "0.1.1" edition = "2024" description = "Java (JDK/JRE) WASM plugin for proto." authors = ["Miles Johnson"] From 56f7b862f12c94b372fe4970bb7ea7350814bc3b Mon Sep 17 00:00:00 2001 From: Miles Johnson Date: Mon, 3 Aug 2026 14:30:00 -0700 Subject: [PATCH 15/78] chore: Release --- Cargo.lock | 2 +- tools/moon/CHANGELOG.md | 2 +- tools/moon/Cargo.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d2c9aecf..1ed00d43 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3273,7 +3273,7 @@ dependencies = [ [[package]] name = "moon_tool" -version = "0.4.2" +version = "0.4.3" dependencies = [ "extism-pdk", "proto_pdk", diff --git a/tools/moon/CHANGELOG.md b/tools/moon/CHANGELOG.md index b2eb5405..a2b189f8 100644 --- a/tools/moon/CHANGELOG.md +++ b/tools/moon/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## Unreleased +## 0.4.3 #### 🚀 Updates diff --git a/tools/moon/Cargo.toml b/tools/moon/Cargo.toml index 1cde8f7e..d254e33a 100644 --- a/tools/moon/Cargo.toml +++ b/tools/moon/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "moon_tool" -version = "0.4.2" +version = "0.4.3" edition = "2024" description = "moon WASM plugin for proto." authors = ["Miles Johnson"] From 86edc5d89c6c58c834ec05fc66635a2d40f35440 Mon Sep 17 00:00:00 2001 From: Miles Johnson Date: Mon, 3 Aug 2026 14:30:13 -0700 Subject: [PATCH 16/78] chore: Release --- Cargo.lock | 2 +- tools/node/CHANGELOG.md | 2 +- tools/node/Cargo.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1ed00d43..c945ef38 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3345,7 +3345,7 @@ dependencies = [ [[package]] name = "node_tool" -version = "0.17.10" +version = "0.17.11" dependencies = [ "extism-pdk", "lang_javascript_common", diff --git a/tools/node/CHANGELOG.md b/tools/node/CHANGELOG.md index 087c5bcb..d5466f9c 100644 --- a/tools/node/CHANGELOG.md +++ b/tools/node/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## Unreleased +## 0.17.11 #### 🚀 Updates diff --git a/tools/node/Cargo.toml b/tools/node/Cargo.toml index c5b53b85..c5ace170 100644 --- a/tools/node/Cargo.toml +++ b/tools/node/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "node_tool" -version = "0.17.10" +version = "0.17.11" edition = "2024" description = "Node.js WASM plugin for proto." authors = ["Miles Johnson"] From f4fd47f386f87be959c960016f037b837f3df9ab Mon Sep 17 00:00:00 2001 From: Miles Johnson Date: Mon, 3 Aug 2026 14:30:25 -0700 Subject: [PATCH 17/78] chore: Release --- Cargo.lock | 2 +- tools/node-depman/CHANGELOG.md | 2 +- tools/node-depman/Cargo.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c945ef38..4d49e748 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3303,7 +3303,7 @@ dependencies = [ [[package]] name = "node_depman_tool" -version = "0.19.0" +version = "0.19.1" dependencies = [ "extism-pdk", "lang_javascript_common", diff --git a/tools/node-depman/CHANGELOG.md b/tools/node-depman/CHANGELOG.md index 01afba33..e3e8ec86 100644 --- a/tools/node-depman/CHANGELOG.md +++ b/tools/node-depman/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## Unreleased +## 0.19.1 #### 🚀 Updates diff --git a/tools/node-depman/Cargo.toml b/tools/node-depman/Cargo.toml index 6b2de4f9..69719d09 100644 --- a/tools/node-depman/Cargo.toml +++ b/tools/node-depman/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "node_depman_tool" -version = "0.19.0" +version = "0.19.1" edition = "2024" description = "Node.js dependency managers (npm, pnpm, yarn) WASM plugin for proto." authors = ["Miles Johnson"] From 9f9d40b96de28905918caff27572ad038eebf958 Mon Sep 17 00:00:00 2001 From: Miles Johnson Date: Mon, 3 Aug 2026 14:30:38 -0700 Subject: [PATCH 18/78] chore: Release --- Cargo.lock | 2 +- tools/proto/CHANGELOG.md | 2 +- tools/proto/Cargo.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4d49e748..3966ed0b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4176,7 +4176,7 @@ dependencies = [ [[package]] name = "proto_tool" -version = "0.5.7" +version = "0.5.8" dependencies = [ "extism-pdk", "proto_pdk", diff --git a/tools/proto/CHANGELOG.md b/tools/proto/CHANGELOG.md index 4fb24864..1cd082e7 100644 --- a/tools/proto/CHANGELOG.md +++ b/tools/proto/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## Unreleased +## 0.5.8 #### 🚀 Updates diff --git a/tools/proto/Cargo.toml b/tools/proto/Cargo.toml index d2968743..8b37c4e9 100644 --- a/tools/proto/Cargo.toml +++ b/tools/proto/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "proto_tool" -version = "0.5.7" +version = "0.5.8" edition = "2024" description = "Internal-only WASM plugin for managing proto itself." authors = ["Miles Johnson"] From 88d80165e2feb09016b3cf429d55ce33d9d899e1 Mon Sep 17 00:00:00 2001 From: Miles Johnson Date: Mon, 3 Aug 2026 14:30:50 -0700 Subject: [PATCH 19/78] chore: Release --- Cargo.lock | 2 +- tools/python/CHANGELOG.md | 2 +- tools/python/Cargo.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3966ed0b..e17b0a4d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4263,7 +4263,7 @@ dependencies = [ [[package]] name = "python_tool" -version = "0.14.8" +version = "0.14.9" dependencies = [ "extism-pdk", "proto_pdk", diff --git a/tools/python/CHANGELOG.md b/tools/python/CHANGELOG.md index cda8e214..379e2d42 100644 --- a/tools/python/CHANGELOG.md +++ b/tools/python/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## Unreleased +## 0.14.9 #### 🚀 Updates diff --git a/tools/python/Cargo.toml b/tools/python/Cargo.toml index da153f6f..be430698 100644 --- a/tools/python/Cargo.toml +++ b/tools/python/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "python_tool" -version = "0.14.8" +version = "0.14.9" edition = "2024" description = "Python WASM plugin for proto." authors = ["Miles Johnson"] From 7918af3f1ed436bec64b18bddc2d35688286975a Mon Sep 17 00:00:00 2001 From: Miles Johnson Date: Mon, 3 Aug 2026 14:31:03 -0700 Subject: [PATCH 20/78] chore: Release --- Cargo.lock | 2 +- tools/python-poetry/CHANGELOG.md | 2 +- tools/python-poetry/Cargo.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e17b0a4d..2aad0015 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4235,7 +4235,7 @@ dependencies = [ [[package]] name = "python_poetry_tool" -version = "0.1.8" +version = "0.1.9" dependencies = [ "extism-pdk", "proto_pdk", diff --git a/tools/python-poetry/CHANGELOG.md b/tools/python-poetry/CHANGELOG.md index 1c85e524..77e7b90d 100644 --- a/tools/python-poetry/CHANGELOG.md +++ b/tools/python-poetry/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## Unreleased +## 0.1.9 #### 🚀 Updates diff --git a/tools/python-poetry/Cargo.toml b/tools/python-poetry/Cargo.toml index b7297695..bcf9818b 100644 --- a/tools/python-poetry/Cargo.toml +++ b/tools/python-poetry/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "python_poetry_tool" -version = "0.1.8" +version = "0.1.9" edition = "2024" description = "Python Poetry WASM plugin for proto." authors = ["Miles Johnson"] From 07a320a3c7a9a1e237ec3f12c0125f794c0fbe79 Mon Sep 17 00:00:00 2001 From: Miles Johnson Date: Mon, 3 Aug 2026 14:31:15 -0700 Subject: [PATCH 21/78] chore: Release --- Cargo.lock | 2 +- tools/python-uv/CHANGELOG.md | 2 +- tools/python-uv/Cargo.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2aad0015..b9c4ca7d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4300,7 +4300,7 @@ dependencies = [ [[package]] name = "python_uv_tool" -version = "0.3.3" +version = "0.3.4" dependencies = [ "extism-pdk", "proto_pdk", diff --git a/tools/python-uv/CHANGELOG.md b/tools/python-uv/CHANGELOG.md index 430cb198..a8591548 100644 --- a/tools/python-uv/CHANGELOG.md +++ b/tools/python-uv/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## Unreleased +## 0.3.4 #### 🚀 Updates diff --git a/tools/python-uv/Cargo.toml b/tools/python-uv/Cargo.toml index 0e5418db..0e7ecf01 100644 --- a/tools/python-uv/Cargo.toml +++ b/tools/python-uv/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "python_uv_tool" -version = "0.3.3" +version = "0.3.4" edition = "2024" description = "Python uv WASM plugin for proto." authors = ["Miles Johnson"] From 4c1f30c5704aff5f1101baf24d32ea3bdfea485c Mon Sep 17 00:00:00 2001 From: Miles Johnson Date: Mon, 3 Aug 2026 14:31:28 -0700 Subject: [PATCH 22/78] chore: Release --- Cargo.lock | 2 +- tools/ruby/CHANGELOG.md | 2 +- tools/ruby/Cargo.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b9c4ca7d..2a58d574 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4731,7 +4731,7 @@ checksum = "64f53e5272cd467a89ad1fa6d66273e3d720477d51875de84e2db7218e370896" [[package]] name = "ruby_tool" -version = "0.2.8" +version = "0.2.9" dependencies = [ "extism-pdk", "proto_pdk", diff --git a/tools/ruby/CHANGELOG.md b/tools/ruby/CHANGELOG.md index 942204bd..cfda5411 100644 --- a/tools/ruby/CHANGELOG.md +++ b/tools/ruby/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## Unreleased +## 0.2.9 #### 🚀 Updates diff --git a/tools/ruby/Cargo.toml b/tools/ruby/Cargo.toml index ae3a8714..abb20fc8 100644 --- a/tools/ruby/Cargo.toml +++ b/tools/ruby/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ruby_tool" -version = "0.2.8" +version = "0.2.9" edition = "2024" description = "Ruby WASM plugin for proto." authors = ["Miles Johnson"] From f2c7af5bbaab819e5f847ba2b2574f1dbe1ce985 Mon Sep 17 00:00:00 2001 From: Miles Johnson Date: Mon, 3 Aug 2026 14:31:40 -0700 Subject: [PATCH 23/78] chore: Release --- Cargo.lock | 2 +- tools/rust/CHANGELOG.md | 2 +- tools/rust/Cargo.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2a58d574..b210936e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4775,7 +4775,7 @@ dependencies = [ [[package]] name = "rust_tool" -version = "0.13.9" +version = "0.13.10" dependencies = [ "extism-pdk", "proto_pdk", diff --git a/tools/rust/CHANGELOG.md b/tools/rust/CHANGELOG.md index 3666a5b7..0bd351ee 100644 --- a/tools/rust/CHANGELOG.md +++ b/tools/rust/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## Unreleased +## 0.13.10 #### 🚀 Updates diff --git a/tools/rust/Cargo.toml b/tools/rust/Cargo.toml index a82aca56..e82f6f52 100644 --- a/tools/rust/Cargo.toml +++ b/tools/rust/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rust_tool" -version = "0.13.9" +version = "0.13.10" edition = "2024" description = "Rust WASM plugin for proto." authors = ["Miles Johnson"] From e3ddf131db12799df0220bd25cb8ef5393fcd677 Mon Sep 17 00:00:00 2001 From: Miles Johnson Date: Mon, 3 Aug 2026 16:18:26 -0700 Subject: [PATCH 24/78] fix: Gracefully handle home detection. --- Cargo.lock | 1 + backends/cargo/CHANGELOG.md | 6 ++++++ backends/cargo/Cargo.toml | 3 ++- backends/cargo/src/proto.rs | 16 +--------------- tools/rust/CHANGELOG.md | 6 ++++++ tools/rust/src/helpers.rs | 4 +++- tools/rust/src/lib.rs | 2 +- tools/rust/src/proto.rs | 1 + 8 files changed, 21 insertions(+), 18 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b210936e..df934450 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -456,6 +456,7 @@ dependencies = [ "extism-pdk", "proto_pdk", "proto_pdk_test_utils 0.48.0", + "rust_tool", "rustc-hash", "schematic", "serde", diff --git a/backends/cargo/CHANGELOG.md b/backends/cargo/CHANGELOG.md index b23dcf96..c06c6d99 100644 --- a/backends/cargo/CHANGELOG.md +++ b/backends/cargo/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## Unreleased + +#### 🚀 Updates + +- Updated to use helpers from the `rust_tool`. + ## 0.1.2 #### 🚀 Updates diff --git a/backends/cargo/Cargo.toml b/backends/cargo/Cargo.toml index fdf30a2c..dfa4ec9e 100644 --- a/backends/cargo/Cargo.toml +++ b/backends/cargo/Cargo.toml @@ -19,6 +19,7 @@ crate-type = ["cdylib", "lib"] [dependencies] backend_common = { path = "../../crates/backend-common" } +rust_tool = { path = "../../tools/rust" } extism-pdk = { workspace = true } proto_pdk = { workspace = true } rustc-hash = { workspace = true } @@ -33,4 +34,4 @@ tokio = { workspace = true } [features] default = ["wasm"] -wasm = [] +wasm = ["rust_tool/wasm"] diff --git a/backends/cargo/src/proto.rs b/backends/cargo/src/proto.rs index 4f5dab31..b374e538 100644 --- a/backends/cargo/src/proto.rs +++ b/backends/cargo/src/proto.rs @@ -2,6 +2,7 @@ use crate::config::{CargoBackendConfig, CargoToolConfig}; use backend_common::enable_tracing; use extism_pdk::*; use proto_pdk::*; +use rust_tool::helpers::get_cargo_home; use schematic::SchemaBuilder; use serde::Deserialize; use starbase_utils::fs; @@ -58,21 +59,6 @@ pub fn define_backend_config() -> FnResult> { })) } -fn get_cargo_home(env: &HostEnvironment) -> Result { - let dir = env.home_dir.join(".cargo"); - - match get_host_env_var("CARGO_HOME")? { - Some(value) => { - if value.is_empty() { - Ok(dir) - } else { - VirtualPath::create(value) - } - } - None => Ok(dir), - } -} - #[plugin_fn] pub fn native_install( Json(input): Json, diff --git a/tools/rust/CHANGELOG.md b/tools/rust/CHANGELOG.md index 0bd351ee..d6cf5056 100644 --- a/tools/rust/CHANGELOG.md +++ b/tools/rust/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## Unreleased + +#### 🐞 Fixes + +- Fixed Cargo/Rustup home directory detection triggering an error. + ## 0.13.10 #### 🚀 Updates diff --git a/tools/rust/src/helpers.rs b/tools/rust/src/helpers.rs index 155facae..a8bb4f55 100644 --- a/tools/rust/src/helpers.rs +++ b/tools/rust/src/helpers.rs @@ -12,7 +12,9 @@ fn get_home_env(key: &str) -> Result, Error> { if value.is_empty() { Ok(None) } else { - VirtualPath::create(value).map(Some) + // This may point to a path outside of our virtual paths, + // which is okay, but we shouldn't fail the entire plugin + Ok(VirtualPath::create(value).ok()) } } None => Ok(None), diff --git a/tools/rust/src/lib.rs b/tools/rust/src/lib.rs index 0f98c15a..c2ef11a9 100644 --- a/tools/rust/src/lib.rs +++ b/tools/rust/src/lib.rs @@ -1,5 +1,5 @@ #[cfg(feature = "wasm")] -mod helpers; +pub mod helpers; #[cfg(feature = "wasm")] mod proto; mod toolchain_toml; diff --git a/tools/rust/src/proto.rs b/tools/rust/src/proto.rs index 910ba72a..55970396 100644 --- a/tools/rust/src/proto.rs +++ b/tools/rust/src/proto.rs @@ -148,6 +148,7 @@ pub fn native_install( // on the host side. This is good enough since it's deterministic. add_host_paths([ get_cargo_home(env)?.join("bin").to_string(), + "$CARGO_HOME/bin".to_string(), "$HOME/.cargo/bin".to_string(), ])?; } From a6c54305f85a998efb675bd344dc0a6112d205da Mon Sep 17 00:00:00 2001 From: Miles Johnson Date: Mon, 3 Aug 2026 16:18:52 -0700 Subject: [PATCH 25/78] chore: Release --- Cargo.lock | 2 +- backends/cargo/CHANGELOG.md | 2 +- backends/cargo/Cargo.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index df934450..c1c63218 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -450,7 +450,7 @@ dependencies = [ [[package]] name = "cargo_backend" -version = "0.1.2" +version = "0.1.3" dependencies = [ "backend_common", "extism-pdk", diff --git a/backends/cargo/CHANGELOG.md b/backends/cargo/CHANGELOG.md index c06c6d99..8ac5b237 100644 --- a/backends/cargo/CHANGELOG.md +++ b/backends/cargo/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## Unreleased +## 0.1.3 #### 🚀 Updates diff --git a/backends/cargo/Cargo.toml b/backends/cargo/Cargo.toml index dfa4ec9e..ce0bd74a 100644 --- a/backends/cargo/Cargo.toml +++ b/backends/cargo/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cargo_backend" -version = "0.1.2" +version = "0.1.3" edition = "2024" description = "Cargo backend WASM plugin for proto, for installing CLIs from crates.io." authors = ["Miles Johnson"] From c92a2b0d927019b082e368a31d848e2d7cbc13b4 Mon Sep 17 00:00:00 2001 From: Miles Johnson Date: Mon, 3 Aug 2026 16:19:07 -0700 Subject: [PATCH 26/78] chore: Release --- Cargo.lock | 2 +- tools/rust/CHANGELOG.md | 2 +- tools/rust/Cargo.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c1c63218..a10cbf20 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4776,7 +4776,7 @@ dependencies = [ [[package]] name = "rust_tool" -version = "0.13.10" +version = "0.13.11" dependencies = [ "extism-pdk", "proto_pdk", diff --git a/tools/rust/CHANGELOG.md b/tools/rust/CHANGELOG.md index d6cf5056..1e2b9304 100644 --- a/tools/rust/CHANGELOG.md +++ b/tools/rust/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## Unreleased +## 0.13.11 #### 🐞 Fixes diff --git a/tools/rust/Cargo.toml b/tools/rust/Cargo.toml index e82f6f52..871e2959 100644 --- a/tools/rust/Cargo.toml +++ b/tools/rust/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rust_tool" -version = "0.13.10" +version = "0.13.11" edition = "2024" description = "Rust WASM plugin for proto." authors = ["Miles Johnson"] From c30fe572040ea8726d8a3e34a0e81f49d0315635 Mon Sep 17 00:00:00 2001 From: Miles Johnson Date: Mon, 3 Aug 2026 17:46:37 -0700 Subject: [PATCH 27/78] fix: Fix cargo feature. --- backends/cargo/CHANGELOG.md | 6 ++++++ backends/cargo/Cargo.toml | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/backends/cargo/CHANGELOG.md b/backends/cargo/CHANGELOG.md index 8ac5b237..8781be37 100644 --- a/backends/cargo/CHANGELOG.md +++ b/backends/cargo/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## Unreleased + +#### 🐞 Fixes + +- Fixed a Cargo features build issue. + ## 0.1.3 #### 🚀 Updates diff --git a/backends/cargo/Cargo.toml b/backends/cargo/Cargo.toml index ce0bd74a..53505b25 100644 --- a/backends/cargo/Cargo.toml +++ b/backends/cargo/Cargo.toml @@ -19,7 +19,7 @@ crate-type = ["cdylib", "lib"] [dependencies] backend_common = { path = "../../crates/backend-common" } -rust_tool = { path = "../../tools/rust" } +rust_tool = { path = "../../tools/rust", default-features = false } extism-pdk = { workspace = true } proto_pdk = { workspace = true } rustc-hash = { workspace = true } From 48c4ced4d26cef9af80f0a42d0da908237adc4d0 Mon Sep 17 00:00:00 2001 From: Miles Johnson Date: Mon, 3 Aug 2026 17:47:00 -0700 Subject: [PATCH 28/78] chore: Release --- Cargo.lock | 2 +- backends/cargo/CHANGELOG.md | 2 +- backends/cargo/Cargo.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a10cbf20..afdd93ef 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -450,7 +450,7 @@ dependencies = [ [[package]] name = "cargo_backend" -version = "0.1.3" +version = "0.1.4" dependencies = [ "backend_common", "extism-pdk", diff --git a/backends/cargo/CHANGELOG.md b/backends/cargo/CHANGELOG.md index 8781be37..34050d4f 100644 --- a/backends/cargo/CHANGELOG.md +++ b/backends/cargo/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## Unreleased +## 0.1.4 #### 🐞 Fixes diff --git a/backends/cargo/Cargo.toml b/backends/cargo/Cargo.toml index 53505b25..f7960619 100644 --- a/backends/cargo/Cargo.toml +++ b/backends/cargo/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cargo_backend" -version = "0.1.3" +version = "0.1.4" edition = "2024" description = "Cargo backend WASM plugin for proto, for installing CLIs from crates.io." authors = ["Miles Johnson"] From 854b4b3259b7853de2f08e660a0ac4cc1f27a7a4 Mon Sep 17 00:00:00 2001 From: Miles Johnson Date: Mon, 3 Aug 2026 22:48:23 -0700 Subject: [PATCH 29/78] new: Add rust common crate. (#175) --- Cargo.lock | 33 ++++++++++++------- Cargo.toml | 2 +- backends/cargo/CHANGELOG.md | 6 ++++ backends/cargo/Cargo.toml | 4 +-- backends/cargo/src/proto.rs | 2 +- crates/lang-rust-common/Cargo.toml | 14 ++++++++ .../lang-rust-common}/src/helpers.rs | 0 crates/lang-rust-common/src/lib.rs | 5 +++ tools/java/src/lib.rs | 1 + tools/rust/CHANGELOG.md | 6 ++++ tools/rust/Cargo.toml | 3 +- tools/rust/src/lib.rs | 2 -- tools/rust/src/proto.rs | 2 +- 13 files changed, 60 insertions(+), 20 deletions(-) create mode 100644 crates/lang-rust-common/Cargo.toml rename {tools/rust => crates/lang-rust-common}/src/helpers.rs (100%) create mode 100644 crates/lang-rust-common/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index afdd93ef..872b4c23 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -454,9 +454,9 @@ version = "0.1.4" dependencies = [ "backend_common", "extism-pdk", + "lang_rust_common", "proto_pdk", "proto_pdk_test_utils 0.48.0", - "rust_tool", "rustc-hash", "schematic", "serde", @@ -2790,6 +2790,14 @@ dependencies = [ "starbase_utils 0.13.8", ] +[[package]] +name = "lang_rust_common" +version = "0.1.0" +dependencies = [ + "extism-pdk", + "proto_pdk", +] + [[package]] name = "lazy_static" version = "1.5.0" @@ -4245,7 +4253,7 @@ dependencies = [ "starbase_sandbox 0.11.1", "starbase_utils 0.13.8", "tokio", - "toml 1.1.3+spec-1.1.0", + "toml 1.1.4+spec-1.1.0", "tool_common", ] @@ -4309,7 +4317,7 @@ dependencies = [ "serde", "starbase_sandbox 0.11.1", "tokio", - "toml 1.1.3+spec-1.1.0", + "toml 1.1.4+spec-1.1.0", "tool_common", ] @@ -4779,13 +4787,14 @@ name = "rust_tool" version = "0.13.11" dependencies = [ "extism-pdk", + "lang_rust_common", "proto_pdk", "proto_pdk_test_utils 0.48.0", "serde", "starbase_sandbox 0.11.1", "starbase_utils 0.13.8", "tokio", - "toml 1.1.3+spec-1.1.0", + "toml 1.1.4+spec-1.1.0", "tool_common", ] @@ -5051,7 +5060,7 @@ dependencies = [ "serde_path_to_error", "starbase_styles", "thiserror 2.0.19", - "toml 1.1.3+spec-1.1.0", + "toml 1.1.4+spec-1.1.0", "tracing", ] @@ -5081,7 +5090,7 @@ dependencies = [ "serde", "serde_json", "serde_norway", - "toml 1.1.3+spec-1.1.0", + "toml 1.1.4+spec-1.1.0", "url", ] @@ -5660,7 +5669,7 @@ dependencies = [ "starbase_styles", "thiserror 2.0.19", "tokio", - "toml 1.1.3+spec-1.1.0", + "toml 1.1.4+spec-1.1.0", "tracing", "url", "wax", @@ -5688,7 +5697,7 @@ dependencies = [ "starbase_styles", "thiserror 2.0.19", "tokio", - "toml 1.1.3+spec-1.1.0", + "toml 1.1.4+spec-1.1.0", "tracing", "url", "wax", @@ -6086,9 +6095,9 @@ dependencies = [ [[package]] name = "toml" -version = "1.1.3+spec-1.1.0" +version = "1.1.4+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53c96ecdfa941c8fc4fcaed14f99ada8ebed502eef533015095a07e3301d4c3c" +checksum = "3aace63f4bbcdfc2c965b059de67119c89c4017a70d633be6c104910f67056f5" dependencies = [ "indexmap", "serde_core", @@ -6132,9 +6141,9 @@ dependencies = [ [[package]] name = "toml_parser" -version = "1.1.2+spec-1.1.0" +version = "1.1.3+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526" +checksum = "1d38ac1cf9b95face32296c0a3ede1fdc270627c9d9c02a7274dd6d960dc4d56" dependencies = [ "winnow 1.0.4", ] diff --git a/Cargo.toml b/Cargo.toml index 78f4b9c7..0b880740 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,7 +19,7 @@ starbase_utils = { version = "0.13.6", default-features = false, features = [ "editor-config", ] } tokio = { version = "1.52.3", features = ["full"] } -toml = { version = "1.1.2", default-features = false, features = [ +toml = { version = "1.1.4", default-features = false, features = [ "parse", "serde", ] } diff --git a/backends/cargo/CHANGELOG.md b/backends/cargo/CHANGELOG.md index 34050d4f..469c4983 100644 --- a/backends/cargo/CHANGELOG.md +++ b/backends/cargo/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## Unreleased + +#### 🚀 Updates + +- Updated to use helpers from the `lang_rust_common` crate. + ## 0.1.4 #### 🐞 Fixes diff --git a/backends/cargo/Cargo.toml b/backends/cargo/Cargo.toml index f7960619..c47d239c 100644 --- a/backends/cargo/Cargo.toml +++ b/backends/cargo/Cargo.toml @@ -19,7 +19,7 @@ crate-type = ["cdylib", "lib"] [dependencies] backend_common = { path = "../../crates/backend-common" } -rust_tool = { path = "../../tools/rust", default-features = false } +lang_rust_common = { path = "../../crates/lang-rust-common" } extism-pdk = { workspace = true } proto_pdk = { workspace = true } rustc-hash = { workspace = true } @@ -34,4 +34,4 @@ tokio = { workspace = true } [features] default = ["wasm"] -wasm = ["rust_tool/wasm"] +wasm = ["lang_rust_common/wasm"] diff --git a/backends/cargo/src/proto.rs b/backends/cargo/src/proto.rs index b374e538..1ed316f0 100644 --- a/backends/cargo/src/proto.rs +++ b/backends/cargo/src/proto.rs @@ -1,8 +1,8 @@ use crate::config::{CargoBackendConfig, CargoToolConfig}; use backend_common::enable_tracing; use extism_pdk::*; +use lang_rust_common::get_cargo_home; use proto_pdk::*; -use rust_tool::helpers::get_cargo_home; use schematic::SchemaBuilder; use serde::Deserialize; use starbase_utils::fs; diff --git a/crates/lang-rust-common/Cargo.toml b/crates/lang-rust-common/Cargo.toml new file mode 100644 index 00000000..2d1d16bf --- /dev/null +++ b/crates/lang-rust-common/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "lang_rust_common" +version = "0.1.0" +edition = "2024" +license = "MIT" +publish = false + +[dependencies] +extism-pdk = { workspace = true } +proto_pdk = { workspace = true } + +[features] +default = [] +wasm = [] diff --git a/tools/rust/src/helpers.rs b/crates/lang-rust-common/src/helpers.rs similarity index 100% rename from tools/rust/src/helpers.rs rename to crates/lang-rust-common/src/helpers.rs diff --git a/crates/lang-rust-common/src/lib.rs b/crates/lang-rust-common/src/lib.rs new file mode 100644 index 00000000..43d6b391 --- /dev/null +++ b/crates/lang-rust-common/src/lib.rs @@ -0,0 +1,5 @@ +#[cfg(feature = "wasm")] +mod helpers; + +#[cfg(feature = "wasm")] +pub use helpers::*; diff --git a/tools/java/src/lib.rs b/tools/java/src/lib.rs index d5d567ee..fc5666e3 100644 --- a/tools/java/src/lib.rs +++ b/tools/java/src/lib.rs @@ -2,6 +2,7 @@ mod config; #[cfg(feature = "wasm")] mod foojay; +#[cfg(feature = "wasm")] mod java; #[cfg(feature = "wasm")] mod proto; diff --git a/tools/rust/CHANGELOG.md b/tools/rust/CHANGELOG.md index 1e2b9304..34cb7f1e 100644 --- a/tools/rust/CHANGELOG.md +++ b/tools/rust/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## Unreleased + +#### 🚀 Updates + +- Updated to use helpers from the `lang_rust_common` crate. + ## 0.13.11 #### 🐞 Fixes diff --git a/tools/rust/Cargo.toml b/tools/rust/Cargo.toml index 871e2959..da15aa0b 100644 --- a/tools/rust/Cargo.toml +++ b/tools/rust/Cargo.toml @@ -18,6 +18,7 @@ pre-release-replacements = [ crate-type = ["cdylib", "lib"] [dependencies] +lang_rust_common = { path = "../../crates/lang-rust-common" } tool_common = { path = "../../crates/tool-common" } extism-pdk = { workspace = true } proto_pdk = { workspace = true } @@ -32,4 +33,4 @@ tokio = { workspace = true } [features] default = ["wasm"] -wasm = [] +wasm = ["lang_rust_common/wasm"] diff --git a/tools/rust/src/lib.rs b/tools/rust/src/lib.rs index c2ef11a9..ac438333 100644 --- a/tools/rust/src/lib.rs +++ b/tools/rust/src/lib.rs @@ -1,6 +1,4 @@ #[cfg(feature = "wasm")] -pub mod helpers; -#[cfg(feature = "wasm")] mod proto; mod toolchain_toml; diff --git a/tools/rust/src/proto.rs b/tools/rust/src/proto.rs index 55970396..05e75d58 100644 --- a/tools/rust/src/proto.rs +++ b/tools/rust/src/proto.rs @@ -1,6 +1,6 @@ -use crate::helpers::*; use crate::toolchain_toml::ToolchainToml; use extism_pdk::*; +use lang_rust_common::*; use proto_pdk::*; use starbase_utils::fs; use std::collections::HashMap; From 946a6ffdfdfe8c508d88b03165ec4300f8e4bcd1 Mon Sep 17 00:00:00 2001 From: Miles Johnson Date: Mon, 3 Aug 2026 22:48:55 -0700 Subject: [PATCH 30/78] chore: Release --- Cargo.lock | 2 +- backends/cargo/CHANGELOG.md | 2 +- backends/cargo/Cargo.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 872b4c23..9049662f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -450,7 +450,7 @@ dependencies = [ [[package]] name = "cargo_backend" -version = "0.1.4" +version = "0.1.5" dependencies = [ "backend_common", "extism-pdk", diff --git a/backends/cargo/CHANGELOG.md b/backends/cargo/CHANGELOG.md index 469c4983..5e113733 100644 --- a/backends/cargo/CHANGELOG.md +++ b/backends/cargo/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## Unreleased +## 0.1.5 #### 🚀 Updates diff --git a/backends/cargo/Cargo.toml b/backends/cargo/Cargo.toml index c47d239c..00e3fc93 100644 --- a/backends/cargo/Cargo.toml +++ b/backends/cargo/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cargo_backend" -version = "0.1.4" +version = "0.1.5" edition = "2024" description = "Cargo backend WASM plugin for proto, for installing CLIs from crates.io." authors = ["Miles Johnson"] From 90abba4cc16c2330d4108f7864c601b62dce4878 Mon Sep 17 00:00:00 2001 From: Miles Johnson Date: Mon, 3 Aug 2026 22:49:08 -0700 Subject: [PATCH 31/78] chore: Release --- Cargo.lock | 2 +- tools/rust/CHANGELOG.md | 2 +- tools/rust/Cargo.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9049662f..2cbdd62f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4784,7 +4784,7 @@ dependencies = [ [[package]] name = "rust_tool" -version = "0.13.11" +version = "0.13.12" dependencies = [ "extism-pdk", "lang_rust_common", diff --git a/tools/rust/CHANGELOG.md b/tools/rust/CHANGELOG.md index 34cb7f1e..86e7032b 100644 --- a/tools/rust/CHANGELOG.md +++ b/tools/rust/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## Unreleased +## 0.13.12 #### 🚀 Updates diff --git a/tools/rust/Cargo.toml b/tools/rust/Cargo.toml index da15aa0b..6bc1f190 100644 --- a/tools/rust/Cargo.toml +++ b/tools/rust/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rust_tool" -version = "0.13.11" +version = "0.13.12" edition = "2024" description = "Rust WASM plugin for proto." authors = ["Miles Johnson"] From 92c393d7b83ab7d9ecefc83b6c40aebfbf944bdc Mon Sep 17 00:00:00 2001 From: Miles Johnson Date: Wed, 5 Aug 2026 12:26:15 -0700 Subject: [PATCH 32/78] new: And generate releases workflow. (#176) --- .github/workflows/ci.yml | 10 ++++---- .github/workflows/generate-releases.yml | 31 +++++++++++++++++++++++++ .github/workflows/release.yml | 2 +- 3 files changed, 37 insertions(+), 6 deletions(-) create mode 100644 .github/workflows/generate-releases.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ee141f9f..20572347 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -21,7 +21,7 @@ jobs: # os: [ubuntu-latest, windows-latest] # fail-fast: false # steps: - # - uses: actions/checkout@v6 + # - uses: actions/checkout@v7 # - uses: moonrepo/setup-rust@v1 # with: # cache: false @@ -35,7 +35,7 @@ jobs: # os: [ubuntu-latest, windows-latest] # fail-fast: false # steps: - # - uses: actions/checkout@v6 + # - uses: actions/checkout@v7 # - uses: moonrepo/setup-rust@v1 # with: # cache: false @@ -49,7 +49,7 @@ jobs: # os: [ubuntu-latest, macos-latest, windows-latest] # fail-fast: false # steps: - # - uses: actions/checkout@v6 + # - uses: actions/checkout@v7 # - uses: moonrepo/setup-rust@v1 # with: # bins: cargo-nextest @@ -66,7 +66,7 @@ jobs: job-total: ${{ steps.plan.outputs.job-total }} jobs-array: ${{ steps.plan.outputs.jobs-array }} steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v7 with: fetch-depth: 0 - uses: moonrepo/setup-toolchain@v0 @@ -85,7 +85,7 @@ jobs: job: ${{ fromJson(needs.plan.outputs.jobs-array) }} fail-fast: false steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v7 with: fetch-depth: 0 - uses: moonrepo/setup-toolchain@v0 diff --git a/.github/workflows/generate-releases.yml b/.github/workflows/generate-releases.yml new file mode 100644 index 00000000..654c12aa --- /dev/null +++ b/.github/workflows/generate-releases.yml @@ -0,0 +1,31 @@ +name: Generate releases + +permissions: + contents: write + +on: + schedule: + # Every 24 hours (avoid the top of the hour, as it's high load for GitHub) + - cron: "15 4 * * *" + workflow_dispatch: + +jobs: + generate: + name: Generate + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v7 + - uses: actions/setup-node@v7 + with: + node-version: 24 + - run: node ./scripts/generatePythonReleases.mjs + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - run: node ./scripts/generateRubyReleases.mjs + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Commit and push changes + uses: EndBug/add-and-commit@v10 + with: + add: 'tools/python/releases.json tools/python/releases-v2.json tools/ruby/releases.json' + message: 'chore: Update tool releases [skip ci]' diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 152b333a..7c7e2159 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -19,7 +19,7 @@ jobs: name: Build runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v7 - uses: moonrepo/setup-rust@v1 with: cache: false From 3191e748cb5eec2a9aeb0114ef73ce148d9f4c08 Mon Sep 17 00:00:00 2001 From: milesj Date: Wed, 5 Aug 2026 21:22:54 +0000 Subject: [PATCH 33/78] chore: Update tool releases [skip ci] --- tools/python/releases-v2.json | 564 +++++++++++++++++++++++++--------- tools/python/releases.json | 508 +++++++++++++++++++++--------- 2 files changed, 796 insertions(+), 276 deletions(-) diff --git a/tools/python/releases-v2.json b/tools/python/releases-v2.json index c8fd2a86..9487a8e2 100644 --- a/tools/python/releases-v2.json +++ b/tools/python/releases-v2.json @@ -600,68 +600,68 @@ }, "3.10.20": { "aarch64-apple-darwin": { - "file": "cpython-3.10.20+20260610-aarch64-apple-darwin-install_only.tar.gz", - "release": "20260610", + "file": "cpython-3.10.20+20260805-aarch64-apple-darwin-install_only.tar.gz", + "release": "20260805", "sha": 1 }, "aarch64-unknown-linux-gnu": { - "file": "cpython-3.10.20+20260610-aarch64-unknown-linux-gnu-install_only.tar.gz", - "release": "20260610", + "file": "cpython-3.10.20+20260805-aarch64-unknown-linux-gnu-install_only.tar.gz", + "release": "20260805", "sha": 1 }, "aarch64-unknown-linux-musl": { - "file": "cpython-3.10.20+20260610-aarch64-unknown-linux-musl-install_only.tar.gz", - "release": "20260610", + "file": "cpython-3.10.20+20260805-aarch64-unknown-linux-musl-install_only.tar.gz", + "release": "20260805", "sha": 1 }, "armv7-unknown-linux-gnueabi": { - "file": "cpython-3.10.20+20260610-armv7-unknown-linux-gnueabi-install_only.tar.gz", - "release": "20260610", + "file": "cpython-3.10.20+20260805-armv7-unknown-linux-gnueabi-install_only.tar.gz", + "release": "20260805", "sha": 1 }, "armv7-unknown-linux-gnueabihf": { - "file": "cpython-3.10.20+20260610-armv7-unknown-linux-gnueabihf-install_only.tar.gz", - "release": "20260610", + "file": "cpython-3.10.20+20260805-armv7-unknown-linux-gnueabihf-install_only.tar.gz", + "release": "20260805", "sha": 1 }, "i686-pc-windows-msvc": { - "file": "cpython-3.10.20+20260610-i686-pc-windows-msvc-install_only.tar.gz", - "release": "20260610", + "file": "cpython-3.10.20+20260805-i686-pc-windows-msvc-install_only.tar.gz", + "release": "20260805", "sha": 1 }, "powerpc64le-unknown-linux-gnu": { - "file": "cpython-3.10.20+20260610-ppc64le-unknown-linux-gnu-install_only.tar.gz", - "release": "20260610", + "file": "cpython-3.10.20+20260805-ppc64le-unknown-linux-gnu-install_only.tar.gz", + "release": "20260805", "sha": 1 }, "riscv64gc-unknown-linux-gnu": { - "file": "cpython-3.10.20+20260610-riscv64-unknown-linux-gnu-install_only.tar.gz", - "release": "20260610", + "file": "cpython-3.10.20+20260805-riscv64-unknown-linux-gnu-install_only.tar.gz", + "release": "20260805", "sha": 1 }, "s390x-unknown-linux-gnu": { - "file": "cpython-3.10.20+20260610-s390x-unknown-linux-gnu-install_only.tar.gz", - "release": "20260610", + "file": "cpython-3.10.20+20260805-s390x-unknown-linux-gnu-install_only.tar.gz", + "release": "20260805", "sha": 1 }, "x86_64-apple-darwin": { - "file": "cpython-3.10.20+20260610-x86_64-apple-darwin-install_only.tar.gz", - "release": "20260610", + "file": "cpython-3.10.20+20260805-x86_64-apple-darwin-install_only.tar.gz", + "release": "20260805", "sha": 1 }, "x86_64-pc-windows-msvc": { - "file": "cpython-3.10.20+20260610-x86_64-pc-windows-msvc-install_only.tar.gz", - "release": "20260610", + "file": "cpython-3.10.20+20260805-x86_64-pc-windows-msvc-install_only.tar.gz", + "release": "20260805", "sha": 1 }, "x86_64-unknown-linux-gnu": { - "file": "cpython-3.10.20+20260610-x86_64-unknown-linux-gnu-install_only.tar.gz", - "release": "20260610", + "file": "cpython-3.10.20+20260805-x86_64-unknown-linux-gnu-install_only.tar.gz", + "release": "20260805", "sha": 1 }, "x86_64-unknown-linux-musl": { - "file": "cpython-3.10.20+20260610-x86_64-unknown-linux-musl-install_only.tar.gz", - "release": "20260610", + "file": "cpython-3.10.20+20260805-x86_64-unknown-linux-musl-install_only.tar.gz", + "release": "20260805", "sha": 1 } }, @@ -1328,73 +1328,73 @@ }, "3.11.15": { "aarch64-apple-darwin": { - "file": "cpython-3.11.15+20260610-aarch64-apple-darwin-install_only.tar.gz", - "release": "20260610", + "file": "cpython-3.11.15+20260805-aarch64-apple-darwin-install_only.tar.gz", + "release": "20260805", "sha": 1 }, "aarch64-pc-windows-msvc": { - "file": "cpython-3.11.15+20260610-aarch64-pc-windows-msvc-install_only.tar.gz", - "release": "20260610", + "file": "cpython-3.11.15+20260805-aarch64-pc-windows-msvc-install_only.tar.gz", + "release": "20260805", "sha": 1 }, "aarch64-unknown-linux-gnu": { - "file": "cpython-3.11.15+20260610-aarch64-unknown-linux-gnu-install_only.tar.gz", - "release": "20260610", + "file": "cpython-3.11.15+20260805-aarch64-unknown-linux-gnu-install_only.tar.gz", + "release": "20260805", "sha": 1 }, "aarch64-unknown-linux-musl": { - "file": "cpython-3.11.15+20260610-aarch64-unknown-linux-musl-install_only.tar.gz", - "release": "20260610", + "file": "cpython-3.11.15+20260805-aarch64-unknown-linux-musl-install_only.tar.gz", + "release": "20260805", "sha": 1 }, "armv7-unknown-linux-gnueabi": { - "file": "cpython-3.11.15+20260610-armv7-unknown-linux-gnueabi-install_only.tar.gz", - "release": "20260610", + "file": "cpython-3.11.15+20260805-armv7-unknown-linux-gnueabi-install_only.tar.gz", + "release": "20260805", "sha": 1 }, "armv7-unknown-linux-gnueabihf": { - "file": "cpython-3.11.15+20260610-armv7-unknown-linux-gnueabihf-install_only.tar.gz", - "release": "20260610", + "file": "cpython-3.11.15+20260805-armv7-unknown-linux-gnueabihf-install_only.tar.gz", + "release": "20260805", "sha": 1 }, "i686-pc-windows-msvc": { - "file": "cpython-3.11.15+20260610-i686-pc-windows-msvc-install_only.tar.gz", - "release": "20260610", + "file": "cpython-3.11.15+20260805-i686-pc-windows-msvc-install_only.tar.gz", + "release": "20260805", "sha": 1 }, "powerpc64le-unknown-linux-gnu": { - "file": "cpython-3.11.15+20260610-ppc64le-unknown-linux-gnu-install_only.tar.gz", - "release": "20260610", + "file": "cpython-3.11.15+20260805-ppc64le-unknown-linux-gnu-install_only.tar.gz", + "release": "20260805", "sha": 1 }, "riscv64gc-unknown-linux-gnu": { - "file": "cpython-3.11.15+20260610-riscv64-unknown-linux-gnu-install_only.tar.gz", - "release": "20260610", + "file": "cpython-3.11.15+20260805-riscv64-unknown-linux-gnu-install_only.tar.gz", + "release": "20260805", "sha": 1 }, "s390x-unknown-linux-gnu": { - "file": "cpython-3.11.15+20260610-s390x-unknown-linux-gnu-install_only.tar.gz", - "release": "20260610", + "file": "cpython-3.11.15+20260805-s390x-unknown-linux-gnu-install_only.tar.gz", + "release": "20260805", "sha": 1 }, "x86_64-apple-darwin": { - "file": "cpython-3.11.15+20260610-x86_64-apple-darwin-install_only.tar.gz", - "release": "20260610", + "file": "cpython-3.11.15+20260805-x86_64-apple-darwin-install_only.tar.gz", + "release": "20260805", "sha": 1 }, "x86_64-pc-windows-msvc": { - "file": "cpython-3.11.15+20260610-x86_64-pc-windows-msvc-install_only.tar.gz", - "release": "20260610", + "file": "cpython-3.11.15+20260805-x86_64-pc-windows-msvc-install_only.tar.gz", + "release": "20260805", "sha": 1 }, "x86_64-unknown-linux-gnu": { - "file": "cpython-3.11.15+20260610-x86_64-unknown-linux-gnu-install_only.tar.gz", - "release": "20260610", + "file": "cpython-3.11.15+20260805-x86_64-unknown-linux-gnu-install_only.tar.gz", + "release": "20260805", "sha": 1 }, "x86_64-unknown-linux-musl": { - "file": "cpython-3.11.15+20260610-x86_64-unknown-linux-musl-install_only.tar.gz", - "release": "20260610", + "file": "cpython-3.11.15+20260805-x86_64-unknown-linux-musl-install_only.tar.gz", + "release": "20260805", "sha": 1 } }, @@ -2049,73 +2049,73 @@ }, "3.12.13": { "aarch64-apple-darwin": { - "file": "cpython-3.12.13+20260610-aarch64-apple-darwin-install_only.tar.gz", - "release": "20260610", + "file": "cpython-3.12.13+20260805-aarch64-apple-darwin-install_only.tar.gz", + "release": "20260805", "sha": 1 }, "aarch64-pc-windows-msvc": { - "file": "cpython-3.12.13+20260610-aarch64-pc-windows-msvc-install_only.tar.gz", - "release": "20260610", + "file": "cpython-3.12.13+20260805-aarch64-pc-windows-msvc-install_only.tar.gz", + "release": "20260805", "sha": 1 }, "aarch64-unknown-linux-gnu": { - "file": "cpython-3.12.13+20260610-aarch64-unknown-linux-gnu-install_only.tar.gz", - "release": "20260610", + "file": "cpython-3.12.13+20260805-aarch64-unknown-linux-gnu-install_only.tar.gz", + "release": "20260805", "sha": 1 }, "aarch64-unknown-linux-musl": { - "file": "cpython-3.12.13+20260610-aarch64-unknown-linux-musl-install_only.tar.gz", - "release": "20260610", + "file": "cpython-3.12.13+20260805-aarch64-unknown-linux-musl-install_only.tar.gz", + "release": "20260805", "sha": 1 }, "armv7-unknown-linux-gnueabi": { - "file": "cpython-3.12.13+20260610-armv7-unknown-linux-gnueabi-install_only.tar.gz", - "release": "20260610", + "file": "cpython-3.12.13+20260805-armv7-unknown-linux-gnueabi-install_only.tar.gz", + "release": "20260805", "sha": 1 }, "armv7-unknown-linux-gnueabihf": { - "file": "cpython-3.12.13+20260610-armv7-unknown-linux-gnueabihf-install_only.tar.gz", - "release": "20260610", + "file": "cpython-3.12.13+20260805-armv7-unknown-linux-gnueabihf-install_only.tar.gz", + "release": "20260805", "sha": 1 }, "i686-pc-windows-msvc": { - "file": "cpython-3.12.13+20260610-i686-pc-windows-msvc-install_only.tar.gz", - "release": "20260610", + "file": "cpython-3.12.13+20260805-i686-pc-windows-msvc-install_only.tar.gz", + "release": "20260805", "sha": 1 }, "powerpc64le-unknown-linux-gnu": { - "file": "cpython-3.12.13+20260610-ppc64le-unknown-linux-gnu-install_only.tar.gz", - "release": "20260610", + "file": "cpython-3.12.13+20260805-ppc64le-unknown-linux-gnu-install_only.tar.gz", + "release": "20260805", "sha": 1 }, "riscv64gc-unknown-linux-gnu": { - "file": "cpython-3.12.13+20260610-riscv64-unknown-linux-gnu-install_only.tar.gz", - "release": "20260610", + "file": "cpython-3.12.13+20260805-riscv64-unknown-linux-gnu-install_only.tar.gz", + "release": "20260805", "sha": 1 }, "s390x-unknown-linux-gnu": { - "file": "cpython-3.12.13+20260610-s390x-unknown-linux-gnu-install_only.tar.gz", - "release": "20260610", + "file": "cpython-3.12.13+20260805-s390x-unknown-linux-gnu-install_only.tar.gz", + "release": "20260805", "sha": 1 }, "x86_64-apple-darwin": { - "file": "cpython-3.12.13+20260610-x86_64-apple-darwin-install_only.tar.gz", - "release": "20260610", + "file": "cpython-3.12.13+20260805-x86_64-apple-darwin-install_only.tar.gz", + "release": "20260805", "sha": 1 }, "x86_64-pc-windows-msvc": { - "file": "cpython-3.12.13+20260610-x86_64-pc-windows-msvc-install_only.tar.gz", - "release": "20260610", + "file": "cpython-3.12.13+20260805-x86_64-pc-windows-msvc-install_only.tar.gz", + "release": "20260805", "sha": 1 }, "x86_64-unknown-linux-gnu": { - "file": "cpython-3.12.13+20260610-x86_64-unknown-linux-gnu-install_only.tar.gz", - "release": "20260610", + "file": "cpython-3.12.13+20260805-x86_64-unknown-linux-gnu-install_only.tar.gz", + "release": "20260805", "sha": 1 }, "x86_64-unknown-linux-musl": { - "file": "cpython-3.12.13+20260610-x86_64-unknown-linux-musl-install_only.tar.gz", - "release": "20260610", + "file": "cpython-3.12.13+20260805-x86_64-unknown-linux-musl-install_only.tar.gz", + "release": "20260805", "sha": 1 } }, @@ -3098,73 +3098,73 @@ }, "3.13.14": { "aarch64-apple-darwin": { - "file": "cpython-3.13.14+20260610-aarch64-apple-darwin-install_only.tar.gz", - "release": "20260610", + "file": "cpython-3.13.14+20260805-aarch64-apple-darwin-install_only.tar.gz", + "release": "20260805", "sha": 1 }, "aarch64-pc-windows-msvc": { - "file": "cpython-3.13.14+20260610-aarch64-pc-windows-msvc-install_only.tar.gz", - "release": "20260610", + "file": "cpython-3.13.14+20260805-aarch64-pc-windows-msvc-install_only.tar.gz", + "release": "20260805", "sha": 1 }, "aarch64-unknown-linux-gnu": { - "file": "cpython-3.13.14+20260610-aarch64-unknown-linux-gnu-install_only.tar.gz", - "release": "20260610", + "file": "cpython-3.13.14+20260805-aarch64-unknown-linux-gnu-install_only.tar.gz", + "release": "20260805", "sha": 1 }, "aarch64-unknown-linux-musl": { - "file": "cpython-3.13.14+20260610-aarch64-unknown-linux-musl-install_only.tar.gz", - "release": "20260610", + "file": "cpython-3.13.14+20260805-aarch64-unknown-linux-musl-install_only.tar.gz", + "release": "20260805", "sha": 1 }, "armv7-unknown-linux-gnueabi": { - "file": "cpython-3.13.14+20260610-armv7-unknown-linux-gnueabi-install_only.tar.gz", - "release": "20260610", + "file": "cpython-3.13.14+20260805-armv7-unknown-linux-gnueabi-install_only.tar.gz", + "release": "20260805", "sha": 1 }, "armv7-unknown-linux-gnueabihf": { - "file": "cpython-3.13.14+20260610-armv7-unknown-linux-gnueabihf-install_only.tar.gz", - "release": "20260610", + "file": "cpython-3.13.14+20260805-armv7-unknown-linux-gnueabihf-install_only.tar.gz", + "release": "20260805", "sha": 1 }, "i686-pc-windows-msvc": { - "file": "cpython-3.13.14+20260610-i686-pc-windows-msvc-install_only.tar.gz", - "release": "20260610", + "file": "cpython-3.13.14+20260805-i686-pc-windows-msvc-install_only.tar.gz", + "release": "20260805", "sha": 1 }, "powerpc64le-unknown-linux-gnu": { - "file": "cpython-3.13.14+20260610-ppc64le-unknown-linux-gnu-install_only.tar.gz", - "release": "20260610", + "file": "cpython-3.13.14+20260805-ppc64le-unknown-linux-gnu-install_only.tar.gz", + "release": "20260805", "sha": 1 }, "riscv64gc-unknown-linux-gnu": { - "file": "cpython-3.13.14+20260610-riscv64-unknown-linux-gnu-install_only.tar.gz", - "release": "20260610", + "file": "cpython-3.13.14+20260805-riscv64-unknown-linux-gnu-install_only.tar.gz", + "release": "20260805", "sha": 1 }, "s390x-unknown-linux-gnu": { - "file": "cpython-3.13.14+20260610-s390x-unknown-linux-gnu-install_only.tar.gz", - "release": "20260610", + "file": "cpython-3.13.14+20260805-s390x-unknown-linux-gnu-install_only.tar.gz", + "release": "20260805", "sha": 1 }, "x86_64-apple-darwin": { - "file": "cpython-3.13.14+20260610-x86_64-apple-darwin-install_only.tar.gz", - "release": "20260610", + "file": "cpython-3.13.14+20260805-x86_64-apple-darwin-install_only.tar.gz", + "release": "20260805", "sha": 1 }, "x86_64-pc-windows-msvc": { - "file": "cpython-3.13.14+20260610-x86_64-pc-windows-msvc-install_only.tar.gz", - "release": "20260610", + "file": "cpython-3.13.14+20260805-x86_64-pc-windows-msvc-install_only.tar.gz", + "release": "20260805", "sha": 1 }, "x86_64-unknown-linux-gnu": { - "file": "cpython-3.13.14+20260610-x86_64-unknown-linux-gnu-install_only.tar.gz", - "release": "20260610", + "file": "cpython-3.13.14+20260805-x86_64-unknown-linux-gnu-install_only.tar.gz", + "release": "20260805", "sha": 1 }, "x86_64-unknown-linux-musl": { - "file": "cpython-3.13.14+20260610-x86_64-unknown-linux-musl-install_only.tar.gz", - "release": "20260610", + "file": "cpython-3.13.14+20260805-x86_64-unknown-linux-musl-install_only.tar.gz", + "release": "20260805", "sha": 1 } }, @@ -4954,73 +4954,145 @@ }, "3.14.6": { "aarch64-apple-darwin": { - "file": "cpython-3.14.6+20260610-aarch64-apple-darwin-install_only.tar.gz", - "release": "20260610", + "file": "cpython-3.14.6+20260804-aarch64-apple-darwin-install_only.tar.gz", + "release": "20260804", "sha": 1 }, "aarch64-pc-windows-msvc": { - "file": "cpython-3.14.6+20260610-aarch64-pc-windows-msvc-install_only.tar.gz", - "release": "20260610", + "file": "cpython-3.14.6+20260804-aarch64-pc-windows-msvc-install_only.tar.gz", + "release": "20260804", "sha": 1 }, "aarch64-unknown-linux-gnu": { - "file": "cpython-3.14.6+20260610-aarch64-unknown-linux-gnu-install_only.tar.gz", - "release": "20260610", + "file": "cpython-3.14.6+20260804-aarch64-unknown-linux-gnu-install_only.tar.gz", + "release": "20260804", "sha": 1 }, "aarch64-unknown-linux-musl": { - "file": "cpython-3.14.6+20260610-aarch64-unknown-linux-musl-install_only.tar.gz", - "release": "20260610", + "file": "cpython-3.14.6+20260804-aarch64-unknown-linux-musl-install_only.tar.gz", + "release": "20260804", "sha": 1 }, "armv7-unknown-linux-gnueabi": { - "file": "cpython-3.14.6+20260610-armv7-unknown-linux-gnueabi-install_only.tar.gz", - "release": "20260610", + "file": "cpython-3.14.6+20260804-armv7-unknown-linux-gnueabi-install_only.tar.gz", + "release": "20260804", "sha": 1 }, "armv7-unknown-linux-gnueabihf": { - "file": "cpython-3.14.6+20260610-armv7-unknown-linux-gnueabihf-install_only.tar.gz", - "release": "20260610", + "file": "cpython-3.14.6+20260804-armv7-unknown-linux-gnueabihf-install_only.tar.gz", + "release": "20260804", "sha": 1 }, "i686-pc-windows-msvc": { - "file": "cpython-3.14.6+20260610-i686-pc-windows-msvc-install_only.tar.gz", - "release": "20260610", + "file": "cpython-3.14.6+20260804-i686-pc-windows-msvc-install_only.tar.gz", + "release": "20260804", "sha": 1 }, "powerpc64le-unknown-linux-gnu": { - "file": "cpython-3.14.6+20260610-ppc64le-unknown-linux-gnu-install_only.tar.gz", - "release": "20260610", + "file": "cpython-3.14.6+20260804-ppc64le-unknown-linux-gnu-install_only.tar.gz", + "release": "20260804", "sha": 1 }, "riscv64gc-unknown-linux-gnu": { - "file": "cpython-3.14.6+20260610-riscv64-unknown-linux-gnu-install_only.tar.gz", - "release": "20260610", + "file": "cpython-3.14.6+20260804-riscv64-unknown-linux-gnu-install_only.tar.gz", + "release": "20260804", "sha": 1 }, "s390x-unknown-linux-gnu": { - "file": "cpython-3.14.6+20260610-s390x-unknown-linux-gnu-install_only.tar.gz", - "release": "20260610", + "file": "cpython-3.14.6+20260804-s390x-unknown-linux-gnu-install_only.tar.gz", + "release": "20260804", "sha": 1 }, "x86_64-apple-darwin": { - "file": "cpython-3.14.6+20260610-x86_64-apple-darwin-install_only.tar.gz", - "release": "20260610", + "file": "cpython-3.14.6+20260804-x86_64-apple-darwin-install_only.tar.gz", + "release": "20260804", "sha": 1 }, "x86_64-pc-windows-msvc": { - "file": "cpython-3.14.6+20260610-x86_64-pc-windows-msvc-install_only.tar.gz", - "release": "20260610", + "file": "cpython-3.14.6+20260804-x86_64-pc-windows-msvc-install_only.tar.gz", + "release": "20260804", "sha": 1 }, "x86_64-unknown-linux-gnu": { - "file": "cpython-3.14.6+20260610-x86_64-unknown-linux-gnu-install_only.tar.gz", - "release": "20260610", + "file": "cpython-3.14.6+20260804-x86_64-unknown-linux-gnu-install_only.tar.gz", + "release": "20260804", "sha": 1 }, "x86_64-unknown-linux-musl": { - "file": "cpython-3.14.6+20260610-x86_64-unknown-linux-musl-install_only.tar.gz", - "release": "20260610", + "file": "cpython-3.14.6+20260804-x86_64-unknown-linux-musl-install_only.tar.gz", + "release": "20260804", + "sha": 1 + } + }, + "3.14.7": { + "aarch64-apple-darwin": { + "file": "cpython-3.14.7+20260805-aarch64-apple-darwin-install_only.tar.gz", + "release": "20260805", + "sha": 1 + }, + "aarch64-pc-windows-msvc": { + "file": "cpython-3.14.7+20260805-aarch64-pc-windows-msvc-install_only.tar.gz", + "release": "20260805", + "sha": 1 + }, + "aarch64-unknown-linux-gnu": { + "file": "cpython-3.14.7+20260805-aarch64-unknown-linux-gnu-install_only.tar.gz", + "release": "20260805", + "sha": 1 + }, + "aarch64-unknown-linux-musl": { + "file": "cpython-3.14.7+20260805-aarch64-unknown-linux-musl-install_only.tar.gz", + "release": "20260805", + "sha": 1 + }, + "armv7-unknown-linux-gnueabi": { + "file": "cpython-3.14.7+20260805-armv7-unknown-linux-gnueabi-install_only.tar.gz", + "release": "20260805", + "sha": 1 + }, + "armv7-unknown-linux-gnueabihf": { + "file": "cpython-3.14.7+20260805-armv7-unknown-linux-gnueabihf-install_only.tar.gz", + "release": "20260805", + "sha": 1 + }, + "i686-pc-windows-msvc": { + "file": "cpython-3.14.7+20260805-i686-pc-windows-msvc-install_only.tar.gz", + "release": "20260805", + "sha": 1 + }, + "powerpc64le-unknown-linux-gnu": { + "file": "cpython-3.14.7+20260805-ppc64le-unknown-linux-gnu-install_only.tar.gz", + "release": "20260805", + "sha": 1 + }, + "riscv64gc-unknown-linux-gnu": { + "file": "cpython-3.14.7+20260805-riscv64-unknown-linux-gnu-install_only.tar.gz", + "release": "20260805", + "sha": 1 + }, + "s390x-unknown-linux-gnu": { + "file": "cpython-3.14.7+20260805-s390x-unknown-linux-gnu-install_only.tar.gz", + "release": "20260805", + "sha": 1 + }, + "x86_64-apple-darwin": { + "file": "cpython-3.14.7+20260805-x86_64-apple-darwin-install_only.tar.gz", + "release": "20260805", + "sha": 1 + }, + "x86_64-pc-windows-msvc": { + "file": "cpython-3.14.7+20260805-x86_64-pc-windows-msvc-install_only.tar.gz", + "release": "20260805", + "sha": 1 + }, + "x86_64-unknown-linux-gnu": { + "file": "cpython-3.14.7+20260805-x86_64-unknown-linux-gnu-install_only.tar.gz", + "release": "20260805", + "sha": 1 + }, + "x86_64-unknown-linux-musl": { + "file": "cpython-3.14.7+20260805-x86_64-unknown-linux-musl-install_only.tar.gz", + "release": "20260805", "sha": 1 } }, @@ -5744,6 +5816,222 @@ "sha": 1 } }, + "3.15.0-b.3": { + "aarch64-apple-darwin": { + "file": "cpython-3.15.0b3+20260623-aarch64-apple-darwin-install_only.tar.gz", + "release": "20260623", + "sha": 1 + }, + "aarch64-pc-windows-msvc": { + "file": "cpython-3.15.0b3+20260623-aarch64-pc-windows-msvc-install_only.tar.gz", + "release": "20260623", + "sha": 1 + }, + "aarch64-unknown-linux-gnu": { + "file": "cpython-3.15.0b3+20260623-aarch64-unknown-linux-gnu-install_only.tar.gz", + "release": "20260623", + "sha": 1 + }, + "aarch64-unknown-linux-musl": { + "file": "cpython-3.15.0b3+20260623-aarch64-unknown-linux-musl-install_only.tar.gz", + "release": "20260623", + "sha": 1 + }, + "armv7-unknown-linux-gnueabi": { + "file": "cpython-3.15.0b3+20260623-armv7-unknown-linux-gnueabi-install_only.tar.gz", + "release": "20260623", + "sha": 1 + }, + "armv7-unknown-linux-gnueabihf": { + "file": "cpython-3.15.0b3+20260623-armv7-unknown-linux-gnueabihf-install_only.tar.gz", + "release": "20260623", + "sha": 1 + }, + "i686-pc-windows-msvc": { + "file": "cpython-3.15.0b3+20260623-i686-pc-windows-msvc-install_only.tar.gz", + "release": "20260623", + "sha": 1 + }, + "powerpc64le-unknown-linux-gnu": { + "file": "cpython-3.15.0b3+20260623-ppc64le-unknown-linux-gnu-install_only.tar.gz", + "release": "20260623", + "sha": 1 + }, + "riscv64gc-unknown-linux-gnu": { + "file": "cpython-3.15.0b3+20260623-riscv64-unknown-linux-gnu-install_only.tar.gz", + "release": "20260623", + "sha": 1 + }, + "s390x-unknown-linux-gnu": { + "file": "cpython-3.15.0b3+20260623-s390x-unknown-linux-gnu-install_only.tar.gz", + "release": "20260623", + "sha": 1 + }, + "x86_64-apple-darwin": { + "file": "cpython-3.15.0b3+20260623-x86_64-apple-darwin-install_only.tar.gz", + "release": "20260623", + "sha": 1 + }, + "x86_64-pc-windows-msvc": { + "file": "cpython-3.15.0b3+20260623-x86_64-pc-windows-msvc-install_only.tar.gz", + "release": "20260623", + "sha": 1 + }, + "x86_64-unknown-linux-gnu": { + "file": "cpython-3.15.0b3+20260623-x86_64-unknown-linux-gnu-install_only.tar.gz", + "release": "20260623", + "sha": 1 + }, + "x86_64-unknown-linux-musl": { + "file": "cpython-3.15.0b3+20260623-x86_64-unknown-linux-musl-install_only.tar.gz", + "release": "20260623", + "sha": 1 + } + }, + "3.15.0-b.4": { + "aarch64-apple-darwin": { + "file": "cpython-3.15.0b4+20260728-aarch64-apple-darwin-install_only.tar.gz", + "release": "20260728", + "sha": 1 + }, + "aarch64-pc-windows-msvc": { + "file": "cpython-3.15.0b4+20260728-aarch64-pc-windows-msvc-install_only.tar.gz", + "release": "20260728", + "sha": 1 + }, + "aarch64-unknown-linux-gnu": { + "file": "cpython-3.15.0b4+20260728-aarch64-unknown-linux-gnu-install_only.tar.gz", + "release": "20260728", + "sha": 1 + }, + "aarch64-unknown-linux-musl": { + "file": "cpython-3.15.0b4+20260728-aarch64-unknown-linux-musl-install_only.tar.gz", + "release": "20260728", + "sha": 1 + }, + "armv7-unknown-linux-gnueabi": { + "file": "cpython-3.15.0b4+20260728-armv7-unknown-linux-gnueabi-install_only.tar.gz", + "release": "20260728", + "sha": 1 + }, + "armv7-unknown-linux-gnueabihf": { + "file": "cpython-3.15.0b4+20260728-armv7-unknown-linux-gnueabihf-install_only.tar.gz", + "release": "20260728", + "sha": 1 + }, + "i686-pc-windows-msvc": { + "file": "cpython-3.15.0b4+20260728-i686-pc-windows-msvc-install_only.tar.gz", + "release": "20260728", + "sha": 1 + }, + "powerpc64le-unknown-linux-gnu": { + "file": "cpython-3.15.0b4+20260728-ppc64le-unknown-linux-gnu-install_only.tar.gz", + "release": "20260728", + "sha": 1 + }, + "riscv64gc-unknown-linux-gnu": { + "file": "cpython-3.15.0b4+20260728-riscv64-unknown-linux-gnu-install_only.tar.gz", + "release": "20260728", + "sha": 1 + }, + "s390x-unknown-linux-gnu": { + "file": "cpython-3.15.0b4+20260728-s390x-unknown-linux-gnu-install_only.tar.gz", + "release": "20260728", + "sha": 1 + }, + "x86_64-apple-darwin": { + "file": "cpython-3.15.0b4+20260728-x86_64-apple-darwin-install_only.tar.gz", + "release": "20260728", + "sha": 1 + }, + "x86_64-pc-windows-msvc": { + "file": "cpython-3.15.0b4+20260728-x86_64-pc-windows-msvc-install_only.tar.gz", + "release": "20260728", + "sha": 1 + }, + "x86_64-unknown-linux-gnu": { + "file": "cpython-3.15.0b4+20260728-x86_64-unknown-linux-gnu-install_only.tar.gz", + "release": "20260728", + "sha": 1 + }, + "x86_64-unknown-linux-musl": { + "file": "cpython-3.15.0b4+20260728-x86_64-unknown-linux-musl-install_only.tar.gz", + "release": "20260728", + "sha": 1 + } + }, + "3.15.0-rc.1": { + "aarch64-apple-darwin": { + "file": "cpython-3.15.0rc1+20260805-aarch64-apple-darwin-install_only.tar.gz", + "release": "20260805", + "sha": 1 + }, + "aarch64-pc-windows-msvc": { + "file": "cpython-3.15.0rc1+20260805-aarch64-pc-windows-msvc-install_only.tar.gz", + "release": "20260805", + "sha": 1 + }, + "aarch64-unknown-linux-gnu": { + "file": "cpython-3.15.0rc1+20260805-aarch64-unknown-linux-gnu-install_only.tar.gz", + "release": "20260805", + "sha": 1 + }, + "aarch64-unknown-linux-musl": { + "file": "cpython-3.15.0rc1+20260805-aarch64-unknown-linux-musl-install_only.tar.gz", + "release": "20260805", + "sha": 1 + }, + "armv7-unknown-linux-gnueabi": { + "file": "cpython-3.15.0rc1+20260805-armv7-unknown-linux-gnueabi-install_only.tar.gz", + "release": "20260805", + "sha": 1 + }, + "armv7-unknown-linux-gnueabihf": { + "file": "cpython-3.15.0rc1+20260805-armv7-unknown-linux-gnueabihf-install_only.tar.gz", + "release": "20260805", + "sha": 1 + }, + "i686-pc-windows-msvc": { + "file": "cpython-3.15.0rc1+20260805-i686-pc-windows-msvc-install_only.tar.gz", + "release": "20260805", + "sha": 1 + }, + "powerpc64le-unknown-linux-gnu": { + "file": "cpython-3.15.0rc1+20260805-ppc64le-unknown-linux-gnu-install_only.tar.gz", + "release": "20260805", + "sha": 1 + }, + "riscv64gc-unknown-linux-gnu": { + "file": "cpython-3.15.0rc1+20260805-riscv64-unknown-linux-gnu-install_only.tar.gz", + "release": "20260805", + "sha": 1 + }, + "s390x-unknown-linux-gnu": { + "file": "cpython-3.15.0rc1+20260805-s390x-unknown-linux-gnu-install_only.tar.gz", + "release": "20260805", + "sha": 1 + }, + "x86_64-apple-darwin": { + "file": "cpython-3.15.0rc1+20260805-x86_64-apple-darwin-install_only.tar.gz", + "release": "20260805", + "sha": 1 + }, + "x86_64-pc-windows-msvc": { + "file": "cpython-3.15.0rc1+20260805-x86_64-pc-windows-msvc-install_only.tar.gz", + "release": "20260805", + "sha": 1 + }, + "x86_64-unknown-linux-gnu": { + "file": "cpython-3.15.0rc1+20260805-x86_64-unknown-linux-gnu-install_only.tar.gz", + "release": "20260805", + "sha": 1 + }, + "x86_64-unknown-linux-musl": { + "file": "cpython-3.15.0rc1+20260805-x86_64-unknown-linux-musl-install_only.tar.gz", + "release": "20260805", + "sha": 1 + } + }, "3.7.6": { "x86_64-pc-windows-msvc": { "file": "cpython-3.7.6-windows-x86-shared-pgo-20200217T0110.tar.zst", diff --git a/tools/python/releases.json b/tools/python/releases.json index 31a15da6..e583f63c 100644 --- a/tools/python/releases.json +++ b/tools/python/releases.json @@ -483,56 +483,56 @@ }, "3.10.20": { "aarch64-apple-darwin": { - "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260610/SHA256SUMS", - "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260610/cpython-3.10.20+20260610-aarch64-apple-darwin-pgo+lto-full.tar.zst" + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/cpython-3.10.20+20260805-aarch64-apple-darwin-pgo+lto-full.tar.zst" }, "aarch64-unknown-linux-gnu": { - "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260610/SHA256SUMS", - "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260610/cpython-3.10.20+20260610-aarch64-unknown-linux-gnu-pgo+lto-full.tar.zst" + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/cpython-3.10.20+20260805-aarch64-unknown-linux-gnu-pgo+lto-full.tar.zst" }, "aarch64-unknown-linux-musl": { - "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260610/SHA256SUMS", - "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260610/cpython-3.10.20+20260610-aarch64-unknown-linux-musl-lto-full.tar.zst" + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/cpython-3.10.20+20260805-aarch64-unknown-linux-musl-lto-full.tar.zst" }, "armv7-unknown-linux-gnueabi": { - "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260610/SHA256SUMS", - "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260610/cpython-3.10.20+20260610-armv7-unknown-linux-gnueabi-lto-full.tar.zst" + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/cpython-3.10.20+20260805-armv7-unknown-linux-gnueabi-lto-full.tar.zst" }, "armv7-unknown-linux-gnueabihf": { - "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260610/SHA256SUMS", - "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260610/cpython-3.10.20+20260610-armv7-unknown-linux-gnueabihf-lto-full.tar.zst" + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/cpython-3.10.20+20260805-armv7-unknown-linux-gnueabihf-lto-full.tar.zst" }, "i686-pc-windows-msvc": { - "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260610/SHA256SUMS", - "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260610/cpython-3.10.20+20260610-i686-pc-windows-msvc-pgo-full.tar.zst" + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/cpython-3.10.20+20260805-i686-pc-windows-msvc-pgo-full.tar.zst" }, "powerpc64le-unknown-linux-gnu": { - "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260610/SHA256SUMS", - "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260610/cpython-3.10.20+20260610-ppc64le-unknown-linux-gnu-lto-full.tar.zst" + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/cpython-3.10.20+20260805-ppc64le-unknown-linux-gnu-lto-full.tar.zst" }, "riscv64gc-unknown-linux-gnu": { - "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260610/SHA256SUMS", - "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260610/cpython-3.10.20+20260610-riscv64-unknown-linux-gnu-lto-full.tar.zst" + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/cpython-3.10.20+20260805-riscv64-unknown-linux-gnu-lto-full.tar.zst" }, "s390x-unknown-linux-gnu": { - "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260610/SHA256SUMS", - "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260610/cpython-3.10.20+20260610-s390x-unknown-linux-gnu-lto-full.tar.zst" + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/cpython-3.10.20+20260805-s390x-unknown-linux-gnu-lto-full.tar.zst" }, "x86_64-apple-darwin": { - "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260610/SHA256SUMS", - "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260610/cpython-3.10.20+20260610-x86_64-apple-darwin-pgo+lto-full.tar.zst" + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/cpython-3.10.20+20260805-x86_64-apple-darwin-pgo+lto-full.tar.zst" }, "x86_64-pc-windows-msvc": { - "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260610/SHA256SUMS", - "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260610/cpython-3.10.20+20260610-x86_64-pc-windows-msvc-pgo-full.tar.zst" + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/cpython-3.10.20+20260805-x86_64-pc-windows-msvc-pgo-full.tar.zst" }, "x86_64-unknown-linux-gnu": { - "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260610/SHA256SUMS", - "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260610/cpython-3.10.20+20260610-x86_64-unknown-linux-gnu-pgo+lto-full.tar.zst" + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/cpython-3.10.20+20260805-x86_64-unknown-linux-gnu-pgo+lto-full.tar.zst" }, "x86_64-unknown-linux-musl": { - "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260610/SHA256SUMS", - "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260610/cpython-3.10.20+20260610-x86_64-unknown-linux-musl-lto-full.tar.zst" + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/cpython-3.10.20+20260805-x86_64-unknown-linux-musl-lto-full.tar.zst" } }, "3.10.3": { @@ -1071,60 +1071,60 @@ }, "3.11.15": { "aarch64-apple-darwin": { - "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260610/SHA256SUMS", - "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260610/cpython-3.11.15+20260610-aarch64-apple-darwin-pgo+lto-full.tar.zst" + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/cpython-3.11.15+20260805-aarch64-apple-darwin-pgo+lto-full.tar.zst" }, "aarch64-pc-windows-msvc": { - "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260610/SHA256SUMS", - "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260610/cpython-3.11.15+20260610-aarch64-pc-windows-msvc-pgo-full.tar.zst" + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/cpython-3.11.15+20260805-aarch64-pc-windows-msvc-pgo-full.tar.zst" }, "aarch64-unknown-linux-gnu": { - "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260610/SHA256SUMS", - "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260610/cpython-3.11.15+20260610-aarch64-unknown-linux-gnu-pgo+lto-full.tar.zst" + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/cpython-3.11.15+20260805-aarch64-unknown-linux-gnu-pgo+lto-full.tar.zst" }, "aarch64-unknown-linux-musl": { - "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260610/SHA256SUMS", - "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260610/cpython-3.11.15+20260610-aarch64-unknown-linux-musl-lto-full.tar.zst" + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/cpython-3.11.15+20260805-aarch64-unknown-linux-musl-lto-full.tar.zst" }, "armv7-unknown-linux-gnueabi": { - "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260610/SHA256SUMS", - "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260610/cpython-3.11.15+20260610-armv7-unknown-linux-gnueabi-lto-full.tar.zst" + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/cpython-3.11.15+20260805-armv7-unknown-linux-gnueabi-lto-full.tar.zst" }, "armv7-unknown-linux-gnueabihf": { - "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260610/SHA256SUMS", - "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260610/cpython-3.11.15+20260610-armv7-unknown-linux-gnueabihf-lto-full.tar.zst" + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/cpython-3.11.15+20260805-armv7-unknown-linux-gnueabihf-lto-full.tar.zst" }, "i686-pc-windows-msvc": { - "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260610/SHA256SUMS", - "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260610/cpython-3.11.15+20260610-i686-pc-windows-msvc-pgo-full.tar.zst" + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/cpython-3.11.15+20260805-i686-pc-windows-msvc-pgo-full.tar.zst" }, "powerpc64le-unknown-linux-gnu": { - "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260610/SHA256SUMS", - "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260610/cpython-3.11.15+20260610-ppc64le-unknown-linux-gnu-lto-full.tar.zst" + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/cpython-3.11.15+20260805-ppc64le-unknown-linux-gnu-lto-full.tar.zst" }, "riscv64gc-unknown-linux-gnu": { - "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260610/SHA256SUMS", - "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260610/cpython-3.11.15+20260610-riscv64-unknown-linux-gnu-lto-full.tar.zst" + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/cpython-3.11.15+20260805-riscv64-unknown-linux-gnu-lto-full.tar.zst" }, "s390x-unknown-linux-gnu": { - "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260610/SHA256SUMS", - "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260610/cpython-3.11.15+20260610-s390x-unknown-linux-gnu-lto-full.tar.zst" + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/cpython-3.11.15+20260805-s390x-unknown-linux-gnu-lto-full.tar.zst" }, "x86_64-apple-darwin": { - "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260610/SHA256SUMS", - "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260610/cpython-3.11.15+20260610-x86_64-apple-darwin-pgo+lto-full.tar.zst" + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/cpython-3.11.15+20260805-x86_64-apple-darwin-pgo+lto-full.tar.zst" }, "x86_64-pc-windows-msvc": { - "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260610/SHA256SUMS", - "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260610/cpython-3.11.15+20260610-x86_64-pc-windows-msvc-pgo-full.tar.zst" + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/cpython-3.11.15+20260805-x86_64-pc-windows-msvc-pgo-full.tar.zst" }, "x86_64-unknown-linux-gnu": { - "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260610/SHA256SUMS", - "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260610/cpython-3.11.15+20260610-x86_64-unknown-linux-gnu-pgo+lto-full.tar.zst" + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/cpython-3.11.15+20260805-x86_64-unknown-linux-gnu-pgo+lto-full.tar.zst" }, "x86_64-unknown-linux-musl": { - "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260610/SHA256SUMS", - "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260610/cpython-3.11.15+20260610-x86_64-unknown-linux-musl-lto-full.tar.zst" + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/cpython-3.11.15+20260805-x86_64-unknown-linux-musl-lto-full.tar.zst" } }, "3.11.3": { @@ -1653,60 +1653,60 @@ }, "3.12.13": { "aarch64-apple-darwin": { - "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260610/SHA256SUMS", - "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260610/cpython-3.12.13+20260610-aarch64-apple-darwin-pgo+lto-full.tar.zst" + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/cpython-3.12.13+20260805-aarch64-apple-darwin-pgo+lto-full.tar.zst" }, "aarch64-pc-windows-msvc": { - "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260610/SHA256SUMS", - "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260610/cpython-3.12.13+20260610-aarch64-pc-windows-msvc-pgo-full.tar.zst" + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/cpython-3.12.13+20260805-aarch64-pc-windows-msvc-pgo-full.tar.zst" }, "aarch64-unknown-linux-gnu": { - "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260610/SHA256SUMS", - "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260610/cpython-3.12.13+20260610-aarch64-unknown-linux-gnu-pgo+lto-full.tar.zst" + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/cpython-3.12.13+20260805-aarch64-unknown-linux-gnu-pgo+lto-full.tar.zst" }, "aarch64-unknown-linux-musl": { - "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260610/SHA256SUMS", - "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260610/cpython-3.12.13+20260610-aarch64-unknown-linux-musl-lto-full.tar.zst" + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/cpython-3.12.13+20260805-aarch64-unknown-linux-musl-lto-full.tar.zst" }, "armv7-unknown-linux-gnueabi": { - "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260610/SHA256SUMS", - "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260610/cpython-3.12.13+20260610-armv7-unknown-linux-gnueabi-lto-full.tar.zst" + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/cpython-3.12.13+20260805-armv7-unknown-linux-gnueabi-lto-full.tar.zst" }, "armv7-unknown-linux-gnueabihf": { - "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260610/SHA256SUMS", - "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260610/cpython-3.12.13+20260610-armv7-unknown-linux-gnueabihf-lto-full.tar.zst" + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/cpython-3.12.13+20260805-armv7-unknown-linux-gnueabihf-lto-full.tar.zst" }, "i686-pc-windows-msvc": { - "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260610/SHA256SUMS", - "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260610/cpython-3.12.13+20260610-i686-pc-windows-msvc-pgo-full.tar.zst" + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/cpython-3.12.13+20260805-i686-pc-windows-msvc-pgo-full.tar.zst" }, "powerpc64le-unknown-linux-gnu": { - "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260610/SHA256SUMS", - "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260610/cpython-3.12.13+20260610-ppc64le-unknown-linux-gnu-lto-full.tar.zst" + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/cpython-3.12.13+20260805-ppc64le-unknown-linux-gnu-lto-full.tar.zst" }, "riscv64gc-unknown-linux-gnu": { - "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260610/SHA256SUMS", - "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260610/cpython-3.12.13+20260610-riscv64-unknown-linux-gnu-lto-full.tar.zst" + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/cpython-3.12.13+20260805-riscv64-unknown-linux-gnu-lto-full.tar.zst" }, "s390x-unknown-linux-gnu": { - "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260610/SHA256SUMS", - "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260610/cpython-3.12.13+20260610-s390x-unknown-linux-gnu-lto-full.tar.zst" + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/cpython-3.12.13+20260805-s390x-unknown-linux-gnu-lto-full.tar.zst" }, "x86_64-apple-darwin": { - "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260610/SHA256SUMS", - "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260610/cpython-3.12.13+20260610-x86_64-apple-darwin-pgo+lto-full.tar.zst" + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/cpython-3.12.13+20260805-x86_64-apple-darwin-pgo+lto-full.tar.zst" }, "x86_64-pc-windows-msvc": { - "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260610/SHA256SUMS", - "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260610/cpython-3.12.13+20260610-x86_64-pc-windows-msvc-pgo-full.tar.zst" + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/cpython-3.12.13+20260805-x86_64-pc-windows-msvc-pgo-full.tar.zst" }, "x86_64-unknown-linux-gnu": { - "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260610/SHA256SUMS", - "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260610/cpython-3.12.13+20260610-x86_64-unknown-linux-gnu-pgo+lto-full.tar.zst" + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/cpython-3.12.13+20260805-x86_64-unknown-linux-gnu-pgo+lto-full.tar.zst" }, "x86_64-unknown-linux-musl": { - "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260610/SHA256SUMS", - "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260610/cpython-3.12.13+20260610-x86_64-unknown-linux-musl-lto-full.tar.zst" + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/cpython-3.12.13+20260805-x86_64-unknown-linux-musl-lto-full.tar.zst" } }, "3.12.2": { @@ -2499,60 +2499,60 @@ }, "3.13.14": { "aarch64-apple-darwin": { - "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260610/SHA256SUMS", - "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260610/cpython-3.13.14+20260610-aarch64-apple-darwin-pgo+lto-full.tar.zst" + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/cpython-3.13.14+20260805-aarch64-apple-darwin-pgo+lto-full.tar.zst" }, "aarch64-pc-windows-msvc": { - "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260610/SHA256SUMS", - "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260610/cpython-3.13.14+20260610-aarch64-pc-windows-msvc-pgo-full.tar.zst" + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/cpython-3.13.14+20260805-aarch64-pc-windows-msvc-pgo-full.tar.zst" }, "aarch64-unknown-linux-gnu": { - "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260610/SHA256SUMS", - "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260610/cpython-3.13.14+20260610-aarch64-unknown-linux-gnu-pgo+lto-full.tar.zst" + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/cpython-3.13.14+20260805-aarch64-unknown-linux-gnu-pgo+lto-full.tar.zst" }, "aarch64-unknown-linux-musl": { - "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260610/SHA256SUMS", - "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260610/cpython-3.13.14+20260610-aarch64-unknown-linux-musl-lto-full.tar.zst" + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/cpython-3.13.14+20260805-aarch64-unknown-linux-musl-lto-full.tar.zst" }, "armv7-unknown-linux-gnueabi": { - "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260610/SHA256SUMS", - "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260610/cpython-3.13.14+20260610-armv7-unknown-linux-gnueabi-lto-full.tar.zst" + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/cpython-3.13.14+20260805-armv7-unknown-linux-gnueabi-lto-full.tar.zst" }, "armv7-unknown-linux-gnueabihf": { - "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260610/SHA256SUMS", - "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260610/cpython-3.13.14+20260610-armv7-unknown-linux-gnueabihf-lto-full.tar.zst" + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/cpython-3.13.14+20260805-armv7-unknown-linux-gnueabihf-lto-full.tar.zst" }, "i686-pc-windows-msvc": { - "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260610/SHA256SUMS", - "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260610/cpython-3.13.14+20260610-i686-pc-windows-msvc-pgo-full.tar.zst" + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/cpython-3.13.14+20260805-i686-pc-windows-msvc-pgo-full.tar.zst" }, "powerpc64le-unknown-linux-gnu": { - "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260610/SHA256SUMS", - "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260610/cpython-3.13.14+20260610-ppc64le-unknown-linux-gnu-lto-full.tar.zst" + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/cpython-3.13.14+20260805-ppc64le-unknown-linux-gnu-lto-full.tar.zst" }, "riscv64gc-unknown-linux-gnu": { - "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260610/SHA256SUMS", - "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260610/cpython-3.13.14+20260610-riscv64-unknown-linux-gnu-lto-full.tar.zst" + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/cpython-3.13.14+20260805-riscv64-unknown-linux-gnu-lto-full.tar.zst" }, "s390x-unknown-linux-gnu": { - "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260610/SHA256SUMS", - "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260610/cpython-3.13.14+20260610-s390x-unknown-linux-gnu-lto-full.tar.zst" + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/cpython-3.13.14+20260805-s390x-unknown-linux-gnu-lto-full.tar.zst" }, "x86_64-apple-darwin": { - "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260610/SHA256SUMS", - "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260610/cpython-3.13.14+20260610-x86_64-apple-darwin-pgo+lto-full.tar.zst" + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/cpython-3.13.14+20260805-x86_64-apple-darwin-pgo+lto-full.tar.zst" }, "x86_64-pc-windows-msvc": { - "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260610/SHA256SUMS", - "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260610/cpython-3.13.14+20260610-x86_64-pc-windows-msvc-pgo-full.tar.zst" + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/cpython-3.13.14+20260805-x86_64-pc-windows-msvc-pgo-full.tar.zst" }, "x86_64-unknown-linux-gnu": { - "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260610/SHA256SUMS", - "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260610/cpython-3.13.14+20260610-x86_64-unknown-linux-gnu-pgo+lto-full.tar.zst" + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/cpython-3.13.14+20260805-x86_64-unknown-linux-gnu-pgo+lto-full.tar.zst" }, "x86_64-unknown-linux-musl": { - "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260610/SHA256SUMS", - "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260610/cpython-3.13.14+20260610-x86_64-unknown-linux-musl-lto-full.tar.zst" + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/cpython-3.13.14+20260805-x86_64-unknown-linux-musl-lto-full.tar.zst" } }, "3.13.2": { @@ -3995,60 +3995,118 @@ }, "3.14.6": { "aarch64-apple-darwin": { - "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260610/SHA256SUMS", - "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260610/cpython-3.14.6+20260610-aarch64-apple-darwin-pgo+lto-full.tar.zst" + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260804/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260804/cpython-3.14.6+20260804-aarch64-apple-darwin-pgo+lto-full.tar.zst" }, "aarch64-pc-windows-msvc": { - "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260610/SHA256SUMS", - "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260610/cpython-3.14.6+20260610-aarch64-pc-windows-msvc-pgo-full.tar.zst" + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260804/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260804/cpython-3.14.6+20260804-aarch64-pc-windows-msvc-pgo-full.tar.zst" }, "aarch64-unknown-linux-gnu": { - "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260610/SHA256SUMS", - "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260610/cpython-3.14.6+20260610-aarch64-unknown-linux-gnu-pgo+lto-full.tar.zst" + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260804/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260804/cpython-3.14.6+20260804-aarch64-unknown-linux-gnu-pgo+lto-full.tar.zst" }, "aarch64-unknown-linux-musl": { - "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260610/SHA256SUMS", - "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260610/cpython-3.14.6+20260610-aarch64-unknown-linux-musl-lto-full.tar.zst" + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260804/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260804/cpython-3.14.6+20260804-aarch64-unknown-linux-musl-lto-full.tar.zst" }, "armv7-unknown-linux-gnueabi": { - "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260610/SHA256SUMS", - "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260610/cpython-3.14.6+20260610-armv7-unknown-linux-gnueabi-lto-full.tar.zst" + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260804/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260804/cpython-3.14.6+20260804-armv7-unknown-linux-gnueabi-lto-full.tar.zst" }, "armv7-unknown-linux-gnueabihf": { - "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260610/SHA256SUMS", - "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260610/cpython-3.14.6+20260610-armv7-unknown-linux-gnueabihf-lto-full.tar.zst" + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260804/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260804/cpython-3.14.6+20260804-armv7-unknown-linux-gnueabihf-lto-full.tar.zst" }, "i686-pc-windows-msvc": { - "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260610/SHA256SUMS", - "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260610/cpython-3.14.6+20260610-i686-pc-windows-msvc-pgo-full.tar.zst" + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260804/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260804/cpython-3.14.6+20260804-i686-pc-windows-msvc-pgo-full.tar.zst" }, "powerpc64le-unknown-linux-gnu": { - "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260610/SHA256SUMS", - "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260610/cpython-3.14.6+20260610-ppc64le-unknown-linux-gnu-lto-full.tar.zst" + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260804/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260804/cpython-3.14.6+20260804-ppc64le-unknown-linux-gnu-lto-full.tar.zst" }, "riscv64gc-unknown-linux-gnu": { - "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260610/SHA256SUMS", - "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260610/cpython-3.14.6+20260610-riscv64-unknown-linux-gnu-lto-full.tar.zst" + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260804/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260804/cpython-3.14.6+20260804-riscv64-unknown-linux-gnu-lto-full.tar.zst" }, "s390x-unknown-linux-gnu": { - "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260610/SHA256SUMS", - "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260610/cpython-3.14.6+20260610-s390x-unknown-linux-gnu-lto-full.tar.zst" + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260804/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260804/cpython-3.14.6+20260804-s390x-unknown-linux-gnu-lto-full.tar.zst" }, "x86_64-apple-darwin": { - "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260610/SHA256SUMS", - "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260610/cpython-3.14.6+20260610-x86_64-apple-darwin-pgo+lto-full.tar.zst" + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260804/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260804/cpython-3.14.6+20260804-x86_64-apple-darwin-pgo+lto-full.tar.zst" }, "x86_64-pc-windows-msvc": { - "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260610/SHA256SUMS", - "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260610/cpython-3.14.6+20260610-x86_64-pc-windows-msvc-pgo-full.tar.zst" + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260804/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260804/cpython-3.14.6+20260804-x86_64-pc-windows-msvc-pgo-full.tar.zst" }, "x86_64-unknown-linux-gnu": { - "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260610/SHA256SUMS", - "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260610/cpython-3.14.6+20260610-x86_64-unknown-linux-gnu-pgo+lto-full.tar.zst" + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260804/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260804/cpython-3.14.6+20260804-x86_64-unknown-linux-gnu-pgo+lto-full.tar.zst" }, "x86_64-unknown-linux-musl": { - "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260610/SHA256SUMS", - "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260610/cpython-3.14.6+20260610-x86_64-unknown-linux-musl-lto-full.tar.zst" + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260804/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260804/cpython-3.14.6+20260804-x86_64-unknown-linux-musl-lto-full.tar.zst" + } + }, + "3.14.7": { + "aarch64-apple-darwin": { + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/cpython-3.14.7+20260805-aarch64-apple-darwin-pgo+lto-full.tar.zst" + }, + "aarch64-pc-windows-msvc": { + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/cpython-3.14.7+20260805-aarch64-pc-windows-msvc-pgo-full.tar.zst" + }, + "aarch64-unknown-linux-gnu": { + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/cpython-3.14.7+20260805-aarch64-unknown-linux-gnu-pgo+lto-full.tar.zst" + }, + "aarch64-unknown-linux-musl": { + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/cpython-3.14.7+20260805-aarch64-unknown-linux-musl-lto-full.tar.zst" + }, + "armv7-unknown-linux-gnueabi": { + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/cpython-3.14.7+20260805-armv7-unknown-linux-gnueabi-lto-full.tar.zst" + }, + "armv7-unknown-linux-gnueabihf": { + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/cpython-3.14.7+20260805-armv7-unknown-linux-gnueabihf-lto-full.tar.zst" + }, + "i686-pc-windows-msvc": { + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/cpython-3.14.7+20260805-i686-pc-windows-msvc-pgo-full.tar.zst" + }, + "powerpc64le-unknown-linux-gnu": { + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/cpython-3.14.7+20260805-ppc64le-unknown-linux-gnu-lto-full.tar.zst" + }, + "riscv64gc-unknown-linux-gnu": { + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/cpython-3.14.7+20260805-riscv64-unknown-linux-gnu-lto-full.tar.zst" + }, + "s390x-unknown-linux-gnu": { + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/cpython-3.14.7+20260805-s390x-unknown-linux-gnu-lto-full.tar.zst" + }, + "x86_64-apple-darwin": { + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/cpython-3.14.7+20260805-x86_64-apple-darwin-pgo+lto-full.tar.zst" + }, + "x86_64-pc-windows-msvc": { + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/cpython-3.14.7+20260805-x86_64-pc-windows-msvc-pgo-full.tar.zst" + }, + "x86_64-unknown-linux-gnu": { + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/cpython-3.14.7+20260805-x86_64-unknown-linux-gnu-pgo+lto-full.tar.zst" + }, + "x86_64-unknown-linux-musl": { + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/cpython-3.14.7+20260805-x86_64-unknown-linux-musl-lto-full.tar.zst" } }, "3.15.0-a.1": { @@ -4631,6 +4689,180 @@ "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260610/cpython-3.15.0b2+20260610-x86_64-unknown-linux-musl-lto-full.tar.zst" } }, + "3.15.0-b.3": { + "aarch64-apple-darwin": { + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260623/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260623/cpython-3.15.0b3+20260623-aarch64-apple-darwin-pgo+lto-full.tar.zst" + }, + "aarch64-pc-windows-msvc": { + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260623/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260623/cpython-3.15.0b3+20260623-aarch64-pc-windows-msvc-pgo-full.tar.zst" + }, + "aarch64-unknown-linux-gnu": { + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260623/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260623/cpython-3.15.0b3+20260623-aarch64-unknown-linux-gnu-pgo+lto-full.tar.zst" + }, + "aarch64-unknown-linux-musl": { + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260623/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260623/cpython-3.15.0b3+20260623-aarch64-unknown-linux-musl-lto-full.tar.zst" + }, + "armv7-unknown-linux-gnueabi": { + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260623/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260623/cpython-3.15.0b3+20260623-armv7-unknown-linux-gnueabi-lto-full.tar.zst" + }, + "armv7-unknown-linux-gnueabihf": { + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260623/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260623/cpython-3.15.0b3+20260623-armv7-unknown-linux-gnueabihf-lto-full.tar.zst" + }, + "i686-pc-windows-msvc": { + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260623/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260623/cpython-3.15.0b3+20260623-i686-pc-windows-msvc-pgo-full.tar.zst" + }, + "powerpc64le-unknown-linux-gnu": { + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260623/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260623/cpython-3.15.0b3+20260623-ppc64le-unknown-linux-gnu-lto-full.tar.zst" + }, + "riscv64gc-unknown-linux-gnu": { + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260623/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260623/cpython-3.15.0b3+20260623-riscv64-unknown-linux-gnu-lto-full.tar.zst" + }, + "s390x-unknown-linux-gnu": { + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260623/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260623/cpython-3.15.0b3+20260623-s390x-unknown-linux-gnu-lto-full.tar.zst" + }, + "x86_64-apple-darwin": { + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260623/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260623/cpython-3.15.0b3+20260623-x86_64-apple-darwin-pgo+lto-full.tar.zst" + }, + "x86_64-pc-windows-msvc": { + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260623/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260623/cpython-3.15.0b3+20260623-x86_64-pc-windows-msvc-pgo-full.tar.zst" + }, + "x86_64-unknown-linux-gnu": { + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260623/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260623/cpython-3.15.0b3+20260623-x86_64-unknown-linux-gnu-pgo+lto-full.tar.zst" + }, + "x86_64-unknown-linux-musl": { + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260623/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260623/cpython-3.15.0b3+20260623-x86_64-unknown-linux-musl-lto-full.tar.zst" + } + }, + "3.15.0-b.4": { + "aarch64-apple-darwin": { + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260728/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260728/cpython-3.15.0b4+20260728-aarch64-apple-darwin-pgo+lto-full.tar.zst" + }, + "aarch64-pc-windows-msvc": { + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260728/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260728/cpython-3.15.0b4+20260728-aarch64-pc-windows-msvc-pgo-full.tar.zst" + }, + "aarch64-unknown-linux-gnu": { + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260728/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260728/cpython-3.15.0b4+20260728-aarch64-unknown-linux-gnu-pgo+lto-full.tar.zst" + }, + "aarch64-unknown-linux-musl": { + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260728/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260728/cpython-3.15.0b4+20260728-aarch64-unknown-linux-musl-lto-full.tar.zst" + }, + "armv7-unknown-linux-gnueabi": { + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260728/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260728/cpython-3.15.0b4+20260728-armv7-unknown-linux-gnueabi-lto-full.tar.zst" + }, + "armv7-unknown-linux-gnueabihf": { + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260728/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260728/cpython-3.15.0b4+20260728-armv7-unknown-linux-gnueabihf-lto-full.tar.zst" + }, + "i686-pc-windows-msvc": { + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260728/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260728/cpython-3.15.0b4+20260728-i686-pc-windows-msvc-pgo-full.tar.zst" + }, + "powerpc64le-unknown-linux-gnu": { + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260728/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260728/cpython-3.15.0b4+20260728-ppc64le-unknown-linux-gnu-lto-full.tar.zst" + }, + "riscv64gc-unknown-linux-gnu": { + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260728/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260728/cpython-3.15.0b4+20260728-riscv64-unknown-linux-gnu-lto-full.tar.zst" + }, + "s390x-unknown-linux-gnu": { + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260728/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260728/cpython-3.15.0b4+20260728-s390x-unknown-linux-gnu-lto-full.tar.zst" + }, + "x86_64-apple-darwin": { + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260728/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260728/cpython-3.15.0b4+20260728-x86_64-apple-darwin-pgo+lto-full.tar.zst" + }, + "x86_64-pc-windows-msvc": { + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260728/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260728/cpython-3.15.0b4+20260728-x86_64-pc-windows-msvc-pgo-full.tar.zst" + }, + "x86_64-unknown-linux-gnu": { + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260728/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260728/cpython-3.15.0b4+20260728-x86_64-unknown-linux-gnu-pgo+lto-full.tar.zst" + }, + "x86_64-unknown-linux-musl": { + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260728/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260728/cpython-3.15.0b4+20260728-x86_64-unknown-linux-musl-lto-full.tar.zst" + } + }, + "3.15.0-rc.1": { + "aarch64-apple-darwin": { + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/cpython-3.15.0rc1+20260805-aarch64-apple-darwin-pgo+lto-full.tar.zst" + }, + "aarch64-pc-windows-msvc": { + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/cpython-3.15.0rc1+20260805-aarch64-pc-windows-msvc-pgo-full.tar.zst" + }, + "aarch64-unknown-linux-gnu": { + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/cpython-3.15.0rc1+20260805-aarch64-unknown-linux-gnu-pgo+lto-full.tar.zst" + }, + "aarch64-unknown-linux-musl": { + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/cpython-3.15.0rc1+20260805-aarch64-unknown-linux-musl-lto-full.tar.zst" + }, + "armv7-unknown-linux-gnueabi": { + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/cpython-3.15.0rc1+20260805-armv7-unknown-linux-gnueabi-lto-full.tar.zst" + }, + "armv7-unknown-linux-gnueabihf": { + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/cpython-3.15.0rc1+20260805-armv7-unknown-linux-gnueabihf-lto-full.tar.zst" + }, + "i686-pc-windows-msvc": { + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/cpython-3.15.0rc1+20260805-i686-pc-windows-msvc-pgo-full.tar.zst" + }, + "powerpc64le-unknown-linux-gnu": { + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/cpython-3.15.0rc1+20260805-ppc64le-unknown-linux-gnu-lto-full.tar.zst" + }, + "riscv64gc-unknown-linux-gnu": { + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/cpython-3.15.0rc1+20260805-riscv64-unknown-linux-gnu-lto-full.tar.zst" + }, + "s390x-unknown-linux-gnu": { + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/cpython-3.15.0rc1+20260805-s390x-unknown-linux-gnu-lto-full.tar.zst" + }, + "x86_64-apple-darwin": { + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/cpython-3.15.0rc1+20260805-x86_64-apple-darwin-pgo+lto-full.tar.zst" + }, + "x86_64-pc-windows-msvc": { + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/cpython-3.15.0rc1+20260805-x86_64-pc-windows-msvc-pgo-full.tar.zst" + }, + "x86_64-unknown-linux-gnu": { + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/cpython-3.15.0rc1+20260805-x86_64-unknown-linux-gnu-pgo+lto-full.tar.zst" + }, + "x86_64-unknown-linux-musl": { + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/cpython-3.15.0rc1+20260805-x86_64-unknown-linux-musl-lto-full.tar.zst" + } + }, "3.7.6": { "x86_64-pc-windows-msvc": { "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20200216/cpython-3.7.6-windows-x86-shared-pgo-20200217T0110.tar.zst" From 0745e31e5fe99f1d2220eed7729382d81f28766f Mon Sep 17 00:00:00 2001 From: milesj Date: Sat, 8 Aug 2026 05:08:47 +0000 Subject: [PATCH 34/78] chore: Update tool releases [skip ci] --- tools/python/releases-v2.json | 348 ++++++++++++++++++++-------------- tools/python/releases.json | 334 ++++++++++++++++++-------------- 2 files changed, 406 insertions(+), 276 deletions(-) diff --git a/tools/python/releases-v2.json b/tools/python/releases-v2.json index 9487a8e2..1ec8e782 100644 --- a/tools/python/releases-v2.json +++ b/tools/python/releases-v2.json @@ -600,68 +600,68 @@ }, "3.10.20": { "aarch64-apple-darwin": { - "file": "cpython-3.10.20+20260805-aarch64-apple-darwin-install_only.tar.gz", - "release": "20260805", + "file": "cpython-3.10.20+20260807-aarch64-apple-darwin-install_only.tar.gz", + "release": "20260807", "sha": 1 }, "aarch64-unknown-linux-gnu": { - "file": "cpython-3.10.20+20260805-aarch64-unknown-linux-gnu-install_only.tar.gz", - "release": "20260805", + "file": "cpython-3.10.20+20260807-aarch64-unknown-linux-gnu-install_only.tar.gz", + "release": "20260807", "sha": 1 }, "aarch64-unknown-linux-musl": { - "file": "cpython-3.10.20+20260805-aarch64-unknown-linux-musl-install_only.tar.gz", - "release": "20260805", + "file": "cpython-3.10.20+20260807-aarch64-unknown-linux-musl-install_only.tar.gz", + "release": "20260807", "sha": 1 }, "armv7-unknown-linux-gnueabi": { - "file": "cpython-3.10.20+20260805-armv7-unknown-linux-gnueabi-install_only.tar.gz", - "release": "20260805", + "file": "cpython-3.10.20+20260807-armv7-unknown-linux-gnueabi-install_only.tar.gz", + "release": "20260807", "sha": 1 }, "armv7-unknown-linux-gnueabihf": { - "file": "cpython-3.10.20+20260805-armv7-unknown-linux-gnueabihf-install_only.tar.gz", - "release": "20260805", + "file": "cpython-3.10.20+20260807-armv7-unknown-linux-gnueabihf-install_only.tar.gz", + "release": "20260807", "sha": 1 }, "i686-pc-windows-msvc": { - "file": "cpython-3.10.20+20260805-i686-pc-windows-msvc-install_only.tar.gz", - "release": "20260805", + "file": "cpython-3.10.20+20260807-i686-pc-windows-msvc-install_only.tar.gz", + "release": "20260807", "sha": 1 }, "powerpc64le-unknown-linux-gnu": { - "file": "cpython-3.10.20+20260805-ppc64le-unknown-linux-gnu-install_only.tar.gz", - "release": "20260805", + "file": "cpython-3.10.20+20260807-ppc64le-unknown-linux-gnu-install_only.tar.gz", + "release": "20260807", "sha": 1 }, "riscv64gc-unknown-linux-gnu": { - "file": "cpython-3.10.20+20260805-riscv64-unknown-linux-gnu-install_only.tar.gz", - "release": "20260805", + "file": "cpython-3.10.20+20260807-riscv64-unknown-linux-gnu-install_only.tar.gz", + "release": "20260807", "sha": 1 }, "s390x-unknown-linux-gnu": { - "file": "cpython-3.10.20+20260805-s390x-unknown-linux-gnu-install_only.tar.gz", - "release": "20260805", + "file": "cpython-3.10.20+20260807-s390x-unknown-linux-gnu-install_only.tar.gz", + "release": "20260807", "sha": 1 }, "x86_64-apple-darwin": { - "file": "cpython-3.10.20+20260805-x86_64-apple-darwin-install_only.tar.gz", - "release": "20260805", + "file": "cpython-3.10.20+20260807-x86_64-apple-darwin-install_only.tar.gz", + "release": "20260807", "sha": 1 }, "x86_64-pc-windows-msvc": { - "file": "cpython-3.10.20+20260805-x86_64-pc-windows-msvc-install_only.tar.gz", - "release": "20260805", + "file": "cpython-3.10.20+20260807-x86_64-pc-windows-msvc-install_only.tar.gz", + "release": "20260807", "sha": 1 }, "x86_64-unknown-linux-gnu": { - "file": "cpython-3.10.20+20260805-x86_64-unknown-linux-gnu-install_only.tar.gz", - "release": "20260805", + "file": "cpython-3.10.20+20260807-x86_64-unknown-linux-gnu-install_only.tar.gz", + "release": "20260807", "sha": 1 }, "x86_64-unknown-linux-musl": { - "file": "cpython-3.10.20+20260805-x86_64-unknown-linux-musl-install_only.tar.gz", - "release": "20260805", + "file": "cpython-3.10.20+20260807-x86_64-unknown-linux-musl-install_only.tar.gz", + "release": "20260807", "sha": 1 } }, @@ -1328,73 +1328,73 @@ }, "3.11.15": { "aarch64-apple-darwin": { - "file": "cpython-3.11.15+20260805-aarch64-apple-darwin-install_only.tar.gz", - "release": "20260805", + "file": "cpython-3.11.15+20260807-aarch64-apple-darwin-install_only.tar.gz", + "release": "20260807", "sha": 1 }, "aarch64-pc-windows-msvc": { - "file": "cpython-3.11.15+20260805-aarch64-pc-windows-msvc-install_only.tar.gz", - "release": "20260805", + "file": "cpython-3.11.15+20260807-aarch64-pc-windows-msvc-install_only.tar.gz", + "release": "20260807", "sha": 1 }, "aarch64-unknown-linux-gnu": { - "file": "cpython-3.11.15+20260805-aarch64-unknown-linux-gnu-install_only.tar.gz", - "release": "20260805", + "file": "cpython-3.11.15+20260807-aarch64-unknown-linux-gnu-install_only.tar.gz", + "release": "20260807", "sha": 1 }, "aarch64-unknown-linux-musl": { - "file": "cpython-3.11.15+20260805-aarch64-unknown-linux-musl-install_only.tar.gz", - "release": "20260805", + "file": "cpython-3.11.15+20260807-aarch64-unknown-linux-musl-install_only.tar.gz", + "release": "20260807", "sha": 1 }, "armv7-unknown-linux-gnueabi": { - "file": "cpython-3.11.15+20260805-armv7-unknown-linux-gnueabi-install_only.tar.gz", - "release": "20260805", + "file": "cpython-3.11.15+20260807-armv7-unknown-linux-gnueabi-install_only.tar.gz", + "release": "20260807", "sha": 1 }, "armv7-unknown-linux-gnueabihf": { - "file": "cpython-3.11.15+20260805-armv7-unknown-linux-gnueabihf-install_only.tar.gz", - "release": "20260805", + "file": "cpython-3.11.15+20260807-armv7-unknown-linux-gnueabihf-install_only.tar.gz", + "release": "20260807", "sha": 1 }, "i686-pc-windows-msvc": { - "file": "cpython-3.11.15+20260805-i686-pc-windows-msvc-install_only.tar.gz", - "release": "20260805", + "file": "cpython-3.11.15+20260807-i686-pc-windows-msvc-install_only.tar.gz", + "release": "20260807", "sha": 1 }, "powerpc64le-unknown-linux-gnu": { - "file": "cpython-3.11.15+20260805-ppc64le-unknown-linux-gnu-install_only.tar.gz", - "release": "20260805", + "file": "cpython-3.11.15+20260807-ppc64le-unknown-linux-gnu-install_only.tar.gz", + "release": "20260807", "sha": 1 }, "riscv64gc-unknown-linux-gnu": { - "file": "cpython-3.11.15+20260805-riscv64-unknown-linux-gnu-install_only.tar.gz", - "release": "20260805", + "file": "cpython-3.11.15+20260807-riscv64-unknown-linux-gnu-install_only.tar.gz", + "release": "20260807", "sha": 1 }, "s390x-unknown-linux-gnu": { - "file": "cpython-3.11.15+20260805-s390x-unknown-linux-gnu-install_only.tar.gz", - "release": "20260805", + "file": "cpython-3.11.15+20260807-s390x-unknown-linux-gnu-install_only.tar.gz", + "release": "20260807", "sha": 1 }, "x86_64-apple-darwin": { - "file": "cpython-3.11.15+20260805-x86_64-apple-darwin-install_only.tar.gz", - "release": "20260805", + "file": "cpython-3.11.15+20260807-x86_64-apple-darwin-install_only.tar.gz", + "release": "20260807", "sha": 1 }, "x86_64-pc-windows-msvc": { - "file": "cpython-3.11.15+20260805-x86_64-pc-windows-msvc-install_only.tar.gz", - "release": "20260805", + "file": "cpython-3.11.15+20260807-x86_64-pc-windows-msvc-install_only.tar.gz", + "release": "20260807", "sha": 1 }, "x86_64-unknown-linux-gnu": { - "file": "cpython-3.11.15+20260805-x86_64-unknown-linux-gnu-install_only.tar.gz", - "release": "20260805", + "file": "cpython-3.11.15+20260807-x86_64-unknown-linux-gnu-install_only.tar.gz", + "release": "20260807", "sha": 1 }, "x86_64-unknown-linux-musl": { - "file": "cpython-3.11.15+20260805-x86_64-unknown-linux-musl-install_only.tar.gz", - "release": "20260805", + "file": "cpython-3.11.15+20260807-x86_64-unknown-linux-musl-install_only.tar.gz", + "release": "20260807", "sha": 1 } }, @@ -2049,73 +2049,73 @@ }, "3.12.13": { "aarch64-apple-darwin": { - "file": "cpython-3.12.13+20260805-aarch64-apple-darwin-install_only.tar.gz", - "release": "20260805", + "file": "cpython-3.12.13+20260807-aarch64-apple-darwin-install_only.tar.gz", + "release": "20260807", "sha": 1 }, "aarch64-pc-windows-msvc": { - "file": "cpython-3.12.13+20260805-aarch64-pc-windows-msvc-install_only.tar.gz", - "release": "20260805", + "file": "cpython-3.12.13+20260807-aarch64-pc-windows-msvc-install_only.tar.gz", + "release": "20260807", "sha": 1 }, "aarch64-unknown-linux-gnu": { - "file": "cpython-3.12.13+20260805-aarch64-unknown-linux-gnu-install_only.tar.gz", - "release": "20260805", + "file": "cpython-3.12.13+20260807-aarch64-unknown-linux-gnu-install_only.tar.gz", + "release": "20260807", "sha": 1 }, "aarch64-unknown-linux-musl": { - "file": "cpython-3.12.13+20260805-aarch64-unknown-linux-musl-install_only.tar.gz", - "release": "20260805", + "file": "cpython-3.12.13+20260807-aarch64-unknown-linux-musl-install_only.tar.gz", + "release": "20260807", "sha": 1 }, "armv7-unknown-linux-gnueabi": { - "file": "cpython-3.12.13+20260805-armv7-unknown-linux-gnueabi-install_only.tar.gz", - "release": "20260805", + "file": "cpython-3.12.13+20260807-armv7-unknown-linux-gnueabi-install_only.tar.gz", + "release": "20260807", "sha": 1 }, "armv7-unknown-linux-gnueabihf": { - "file": "cpython-3.12.13+20260805-armv7-unknown-linux-gnueabihf-install_only.tar.gz", - "release": "20260805", + "file": "cpython-3.12.13+20260807-armv7-unknown-linux-gnueabihf-install_only.tar.gz", + "release": "20260807", "sha": 1 }, "i686-pc-windows-msvc": { - "file": "cpython-3.12.13+20260805-i686-pc-windows-msvc-install_only.tar.gz", - "release": "20260805", + "file": "cpython-3.12.13+20260807-i686-pc-windows-msvc-install_only.tar.gz", + "release": "20260807", "sha": 1 }, "powerpc64le-unknown-linux-gnu": { - "file": "cpython-3.12.13+20260805-ppc64le-unknown-linux-gnu-install_only.tar.gz", - "release": "20260805", + "file": "cpython-3.12.13+20260807-ppc64le-unknown-linux-gnu-install_only.tar.gz", + "release": "20260807", "sha": 1 }, "riscv64gc-unknown-linux-gnu": { - "file": "cpython-3.12.13+20260805-riscv64-unknown-linux-gnu-install_only.tar.gz", - "release": "20260805", + "file": "cpython-3.12.13+20260807-riscv64-unknown-linux-gnu-install_only.tar.gz", + "release": "20260807", "sha": 1 }, "s390x-unknown-linux-gnu": { - "file": "cpython-3.12.13+20260805-s390x-unknown-linux-gnu-install_only.tar.gz", - "release": "20260805", + "file": "cpython-3.12.13+20260807-s390x-unknown-linux-gnu-install_only.tar.gz", + "release": "20260807", "sha": 1 }, "x86_64-apple-darwin": { - "file": "cpython-3.12.13+20260805-x86_64-apple-darwin-install_only.tar.gz", - "release": "20260805", + "file": "cpython-3.12.13+20260807-x86_64-apple-darwin-install_only.tar.gz", + "release": "20260807", "sha": 1 }, "x86_64-pc-windows-msvc": { - "file": "cpython-3.12.13+20260805-x86_64-pc-windows-msvc-install_only.tar.gz", - "release": "20260805", + "file": "cpython-3.12.13+20260807-x86_64-pc-windows-msvc-install_only.tar.gz", + "release": "20260807", "sha": 1 }, "x86_64-unknown-linux-gnu": { - "file": "cpython-3.12.13+20260805-x86_64-unknown-linux-gnu-install_only.tar.gz", - "release": "20260805", + "file": "cpython-3.12.13+20260807-x86_64-unknown-linux-gnu-install_only.tar.gz", + "release": "20260807", "sha": 1 }, "x86_64-unknown-linux-musl": { - "file": "cpython-3.12.13+20260805-x86_64-unknown-linux-musl-install_only.tar.gz", - "release": "20260805", + "file": "cpython-3.12.13+20260807-x86_64-unknown-linux-musl-install_only.tar.gz", + "release": "20260807", "sha": 1 } }, @@ -3168,6 +3168,78 @@ "sha": 1 } }, + "3.13.15": { + "aarch64-apple-darwin": { + "file": "cpython-3.13.15+20260807-aarch64-apple-darwin-install_only.tar.gz", + "release": "20260807", + "sha": 1 + }, + "aarch64-pc-windows-msvc": { + "file": "cpython-3.13.15+20260807-aarch64-pc-windows-msvc-install_only.tar.gz", + "release": "20260807", + "sha": 1 + }, + "aarch64-unknown-linux-gnu": { + "file": "cpython-3.13.15+20260807-aarch64-unknown-linux-gnu-install_only.tar.gz", + "release": "20260807", + "sha": 1 + }, + "aarch64-unknown-linux-musl": { + "file": "cpython-3.13.15+20260807-aarch64-unknown-linux-musl-install_only.tar.gz", + "release": "20260807", + "sha": 1 + }, + "armv7-unknown-linux-gnueabi": { + "file": "cpython-3.13.15+20260807-armv7-unknown-linux-gnueabi-install_only.tar.gz", + "release": "20260807", + "sha": 1 + }, + "armv7-unknown-linux-gnueabihf": { + "file": "cpython-3.13.15+20260807-armv7-unknown-linux-gnueabihf-install_only.tar.gz", + "release": "20260807", + "sha": 1 + }, + "i686-pc-windows-msvc": { + "file": "cpython-3.13.15+20260807-i686-pc-windows-msvc-install_only.tar.gz", + "release": "20260807", + "sha": 1 + }, + "powerpc64le-unknown-linux-gnu": { + "file": "cpython-3.13.15+20260807-ppc64le-unknown-linux-gnu-install_only.tar.gz", + "release": "20260807", + "sha": 1 + }, + "riscv64gc-unknown-linux-gnu": { + "file": "cpython-3.13.15+20260807-riscv64-unknown-linux-gnu-install_only.tar.gz", + "release": "20260807", + "sha": 1 + }, + "s390x-unknown-linux-gnu": { + "file": "cpython-3.13.15+20260807-s390x-unknown-linux-gnu-install_only.tar.gz", + "release": "20260807", + "sha": 1 + }, + "x86_64-apple-darwin": { + "file": "cpython-3.13.15+20260807-x86_64-apple-darwin-install_only.tar.gz", + "release": "20260807", + "sha": 1 + }, + "x86_64-pc-windows-msvc": { + "file": "cpython-3.13.15+20260807-x86_64-pc-windows-msvc-install_only.tar.gz", + "release": "20260807", + "sha": 1 + }, + "x86_64-unknown-linux-gnu": { + "file": "cpython-3.13.15+20260807-x86_64-unknown-linux-gnu-install_only.tar.gz", + "release": "20260807", + "sha": 1 + }, + "x86_64-unknown-linux-musl": { + "file": "cpython-3.13.15+20260807-x86_64-unknown-linux-musl-install_only.tar.gz", + "release": "20260807", + "sha": 1 + } + }, "3.13.2": { "aarch64-apple-darwin": { "file": "cpython-3.13.2+20250317-aarch64-apple-darwin-install_only.tar.gz", @@ -5026,73 +5098,73 @@ }, "3.14.7": { "aarch64-apple-darwin": { - "file": "cpython-3.14.7+20260805-aarch64-apple-darwin-install_only.tar.gz", - "release": "20260805", + "file": "cpython-3.14.7+20260807-aarch64-apple-darwin-install_only.tar.gz", + "release": "20260807", "sha": 1 }, "aarch64-pc-windows-msvc": { - "file": "cpython-3.14.7+20260805-aarch64-pc-windows-msvc-install_only.tar.gz", - "release": "20260805", + "file": "cpython-3.14.7+20260807-aarch64-pc-windows-msvc-install_only.tar.gz", + "release": "20260807", "sha": 1 }, "aarch64-unknown-linux-gnu": { - "file": "cpython-3.14.7+20260805-aarch64-unknown-linux-gnu-install_only.tar.gz", - "release": "20260805", + "file": "cpython-3.14.7+20260807-aarch64-unknown-linux-gnu-install_only.tar.gz", + "release": "20260807", "sha": 1 }, "aarch64-unknown-linux-musl": { - "file": "cpython-3.14.7+20260805-aarch64-unknown-linux-musl-install_only.tar.gz", - "release": "20260805", + "file": "cpython-3.14.7+20260807-aarch64-unknown-linux-musl-install_only.tar.gz", + "release": "20260807", "sha": 1 }, "armv7-unknown-linux-gnueabi": { - "file": "cpython-3.14.7+20260805-armv7-unknown-linux-gnueabi-install_only.tar.gz", - "release": "20260805", + "file": "cpython-3.14.7+20260807-armv7-unknown-linux-gnueabi-install_only.tar.gz", + "release": "20260807", "sha": 1 }, "armv7-unknown-linux-gnueabihf": { - "file": "cpython-3.14.7+20260805-armv7-unknown-linux-gnueabihf-install_only.tar.gz", - "release": "20260805", + "file": "cpython-3.14.7+20260807-armv7-unknown-linux-gnueabihf-install_only.tar.gz", + "release": "20260807", "sha": 1 }, "i686-pc-windows-msvc": { - "file": "cpython-3.14.7+20260805-i686-pc-windows-msvc-install_only.tar.gz", - "release": "20260805", + "file": "cpython-3.14.7+20260807-i686-pc-windows-msvc-install_only.tar.gz", + "release": "20260807", "sha": 1 }, "powerpc64le-unknown-linux-gnu": { - "file": "cpython-3.14.7+20260805-ppc64le-unknown-linux-gnu-install_only.tar.gz", - "release": "20260805", + "file": "cpython-3.14.7+20260807-ppc64le-unknown-linux-gnu-install_only.tar.gz", + "release": "20260807", "sha": 1 }, "riscv64gc-unknown-linux-gnu": { - "file": "cpython-3.14.7+20260805-riscv64-unknown-linux-gnu-install_only.tar.gz", - "release": "20260805", + "file": "cpython-3.14.7+20260807-riscv64-unknown-linux-gnu-install_only.tar.gz", + "release": "20260807", "sha": 1 }, "s390x-unknown-linux-gnu": { - "file": "cpython-3.14.7+20260805-s390x-unknown-linux-gnu-install_only.tar.gz", - "release": "20260805", + "file": "cpython-3.14.7+20260807-s390x-unknown-linux-gnu-install_only.tar.gz", + "release": "20260807", "sha": 1 }, "x86_64-apple-darwin": { - "file": "cpython-3.14.7+20260805-x86_64-apple-darwin-install_only.tar.gz", - "release": "20260805", + "file": "cpython-3.14.7+20260807-x86_64-apple-darwin-install_only.tar.gz", + "release": "20260807", "sha": 1 }, "x86_64-pc-windows-msvc": { - "file": "cpython-3.14.7+20260805-x86_64-pc-windows-msvc-install_only.tar.gz", - "release": "20260805", + "file": "cpython-3.14.7+20260807-x86_64-pc-windows-msvc-install_only.tar.gz", + "release": "20260807", "sha": 1 }, "x86_64-unknown-linux-gnu": { - "file": "cpython-3.14.7+20260805-x86_64-unknown-linux-gnu-install_only.tar.gz", - "release": "20260805", + "file": "cpython-3.14.7+20260807-x86_64-unknown-linux-gnu-install_only.tar.gz", + "release": "20260807", "sha": 1 }, "x86_64-unknown-linux-musl": { - "file": "cpython-3.14.7+20260805-x86_64-unknown-linux-musl-install_only.tar.gz", - "release": "20260805", + "file": "cpython-3.14.7+20260807-x86_64-unknown-linux-musl-install_only.tar.gz", + "release": "20260807", "sha": 1 } }, @@ -5962,73 +6034,73 @@ }, "3.15.0-rc.1": { "aarch64-apple-darwin": { - "file": "cpython-3.15.0rc1+20260805-aarch64-apple-darwin-install_only.tar.gz", - "release": "20260805", + "file": "cpython-3.15.0rc1+20260807-aarch64-apple-darwin-install_only.tar.gz", + "release": "20260807", "sha": 1 }, "aarch64-pc-windows-msvc": { - "file": "cpython-3.15.0rc1+20260805-aarch64-pc-windows-msvc-install_only.tar.gz", - "release": "20260805", + "file": "cpython-3.15.0rc1+20260807-aarch64-pc-windows-msvc-install_only.tar.gz", + "release": "20260807", "sha": 1 }, "aarch64-unknown-linux-gnu": { - "file": "cpython-3.15.0rc1+20260805-aarch64-unknown-linux-gnu-install_only.tar.gz", - "release": "20260805", + "file": "cpython-3.15.0rc1+20260807-aarch64-unknown-linux-gnu-install_only.tar.gz", + "release": "20260807", "sha": 1 }, "aarch64-unknown-linux-musl": { - "file": "cpython-3.15.0rc1+20260805-aarch64-unknown-linux-musl-install_only.tar.gz", - "release": "20260805", + "file": "cpython-3.15.0rc1+20260807-aarch64-unknown-linux-musl-install_only.tar.gz", + "release": "20260807", "sha": 1 }, "armv7-unknown-linux-gnueabi": { - "file": "cpython-3.15.0rc1+20260805-armv7-unknown-linux-gnueabi-install_only.tar.gz", - "release": "20260805", + "file": "cpython-3.15.0rc1+20260807-armv7-unknown-linux-gnueabi-install_only.tar.gz", + "release": "20260807", "sha": 1 }, "armv7-unknown-linux-gnueabihf": { - "file": "cpython-3.15.0rc1+20260805-armv7-unknown-linux-gnueabihf-install_only.tar.gz", - "release": "20260805", + "file": "cpython-3.15.0rc1+20260807-armv7-unknown-linux-gnueabihf-install_only.tar.gz", + "release": "20260807", "sha": 1 }, "i686-pc-windows-msvc": { - "file": "cpython-3.15.0rc1+20260805-i686-pc-windows-msvc-install_only.tar.gz", - "release": "20260805", + "file": "cpython-3.15.0rc1+20260807-i686-pc-windows-msvc-install_only.tar.gz", + "release": "20260807", "sha": 1 }, "powerpc64le-unknown-linux-gnu": { - "file": "cpython-3.15.0rc1+20260805-ppc64le-unknown-linux-gnu-install_only.tar.gz", - "release": "20260805", + "file": "cpython-3.15.0rc1+20260807-ppc64le-unknown-linux-gnu-install_only.tar.gz", + "release": "20260807", "sha": 1 }, "riscv64gc-unknown-linux-gnu": { - "file": "cpython-3.15.0rc1+20260805-riscv64-unknown-linux-gnu-install_only.tar.gz", - "release": "20260805", + "file": "cpython-3.15.0rc1+20260807-riscv64-unknown-linux-gnu-install_only.tar.gz", + "release": "20260807", "sha": 1 }, "s390x-unknown-linux-gnu": { - "file": "cpython-3.15.0rc1+20260805-s390x-unknown-linux-gnu-install_only.tar.gz", - "release": "20260805", + "file": "cpython-3.15.0rc1+20260807-s390x-unknown-linux-gnu-install_only.tar.gz", + "release": "20260807", "sha": 1 }, "x86_64-apple-darwin": { - "file": "cpython-3.15.0rc1+20260805-x86_64-apple-darwin-install_only.tar.gz", - "release": "20260805", + "file": "cpython-3.15.0rc1+20260807-x86_64-apple-darwin-install_only.tar.gz", + "release": "20260807", "sha": 1 }, "x86_64-pc-windows-msvc": { - "file": "cpython-3.15.0rc1+20260805-x86_64-pc-windows-msvc-install_only.tar.gz", - "release": "20260805", + "file": "cpython-3.15.0rc1+20260807-x86_64-pc-windows-msvc-install_only.tar.gz", + "release": "20260807", "sha": 1 }, "x86_64-unknown-linux-gnu": { - "file": "cpython-3.15.0rc1+20260805-x86_64-unknown-linux-gnu-install_only.tar.gz", - "release": "20260805", + "file": "cpython-3.15.0rc1+20260807-x86_64-unknown-linux-gnu-install_only.tar.gz", + "release": "20260807", "sha": 1 }, "x86_64-unknown-linux-musl": { - "file": "cpython-3.15.0rc1+20260805-x86_64-unknown-linux-musl-install_only.tar.gz", - "release": "20260805", + "file": "cpython-3.15.0rc1+20260807-x86_64-unknown-linux-musl-install_only.tar.gz", + "release": "20260807", "sha": 1 } }, diff --git a/tools/python/releases.json b/tools/python/releases.json index e583f63c..1364b6ea 100644 --- a/tools/python/releases.json +++ b/tools/python/releases.json @@ -483,56 +483,56 @@ }, "3.10.20": { "aarch64-apple-darwin": { - "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/SHA256SUMS", - "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/cpython-3.10.20+20260805-aarch64-apple-darwin-pgo+lto-full.tar.zst" + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/cpython-3.10.20+20260807-aarch64-apple-darwin-pgo+lto-full.tar.zst" }, "aarch64-unknown-linux-gnu": { - "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/SHA256SUMS", - "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/cpython-3.10.20+20260805-aarch64-unknown-linux-gnu-pgo+lto-full.tar.zst" + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/cpython-3.10.20+20260807-aarch64-unknown-linux-gnu-pgo+lto-full.tar.zst" }, "aarch64-unknown-linux-musl": { - "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/SHA256SUMS", - "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/cpython-3.10.20+20260805-aarch64-unknown-linux-musl-lto-full.tar.zst" + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/cpython-3.10.20+20260807-aarch64-unknown-linux-musl-lto-full.tar.zst" }, "armv7-unknown-linux-gnueabi": { - "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/SHA256SUMS", - "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/cpython-3.10.20+20260805-armv7-unknown-linux-gnueabi-lto-full.tar.zst" + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/cpython-3.10.20+20260807-armv7-unknown-linux-gnueabi-lto-full.tar.zst" }, "armv7-unknown-linux-gnueabihf": { - "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/SHA256SUMS", - "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/cpython-3.10.20+20260805-armv7-unknown-linux-gnueabihf-lto-full.tar.zst" + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/cpython-3.10.20+20260807-armv7-unknown-linux-gnueabihf-lto-full.tar.zst" }, "i686-pc-windows-msvc": { - "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/SHA256SUMS", - "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/cpython-3.10.20+20260805-i686-pc-windows-msvc-pgo-full.tar.zst" + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/cpython-3.10.20+20260807-i686-pc-windows-msvc-pgo-full.tar.zst" }, "powerpc64le-unknown-linux-gnu": { - "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/SHA256SUMS", - "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/cpython-3.10.20+20260805-ppc64le-unknown-linux-gnu-lto-full.tar.zst" + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/cpython-3.10.20+20260807-ppc64le-unknown-linux-gnu-lto-full.tar.zst" }, "riscv64gc-unknown-linux-gnu": { - "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/SHA256SUMS", - "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/cpython-3.10.20+20260805-riscv64-unknown-linux-gnu-lto-full.tar.zst" + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/cpython-3.10.20+20260807-riscv64-unknown-linux-gnu-lto-full.tar.zst" }, "s390x-unknown-linux-gnu": { - "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/SHA256SUMS", - "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/cpython-3.10.20+20260805-s390x-unknown-linux-gnu-lto-full.tar.zst" + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/cpython-3.10.20+20260807-s390x-unknown-linux-gnu-lto-full.tar.zst" }, "x86_64-apple-darwin": { - "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/SHA256SUMS", - "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/cpython-3.10.20+20260805-x86_64-apple-darwin-pgo+lto-full.tar.zst" + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/cpython-3.10.20+20260807-x86_64-apple-darwin-pgo+lto-full.tar.zst" }, "x86_64-pc-windows-msvc": { - "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/SHA256SUMS", - "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/cpython-3.10.20+20260805-x86_64-pc-windows-msvc-pgo-full.tar.zst" + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/cpython-3.10.20+20260807-x86_64-pc-windows-msvc-pgo-full.tar.zst" }, "x86_64-unknown-linux-gnu": { - "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/SHA256SUMS", - "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/cpython-3.10.20+20260805-x86_64-unknown-linux-gnu-pgo+lto-full.tar.zst" + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/cpython-3.10.20+20260807-x86_64-unknown-linux-gnu-pgo+lto-full.tar.zst" }, "x86_64-unknown-linux-musl": { - "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/SHA256SUMS", - "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/cpython-3.10.20+20260805-x86_64-unknown-linux-musl-lto-full.tar.zst" + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/cpython-3.10.20+20260807-x86_64-unknown-linux-musl-lto-full.tar.zst" } }, "3.10.3": { @@ -1071,60 +1071,60 @@ }, "3.11.15": { "aarch64-apple-darwin": { - "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/SHA256SUMS", - "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/cpython-3.11.15+20260805-aarch64-apple-darwin-pgo+lto-full.tar.zst" + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/cpython-3.11.15+20260807-aarch64-apple-darwin-pgo+lto-full.tar.zst" }, "aarch64-pc-windows-msvc": { - "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/SHA256SUMS", - "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/cpython-3.11.15+20260805-aarch64-pc-windows-msvc-pgo-full.tar.zst" + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/cpython-3.11.15+20260807-aarch64-pc-windows-msvc-pgo-full.tar.zst" }, "aarch64-unknown-linux-gnu": { - "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/SHA256SUMS", - "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/cpython-3.11.15+20260805-aarch64-unknown-linux-gnu-pgo+lto-full.tar.zst" + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/cpython-3.11.15+20260807-aarch64-unknown-linux-gnu-pgo+lto-full.tar.zst" }, "aarch64-unknown-linux-musl": { - "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/SHA256SUMS", - "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/cpython-3.11.15+20260805-aarch64-unknown-linux-musl-lto-full.tar.zst" + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/cpython-3.11.15+20260807-aarch64-unknown-linux-musl-lto-full.tar.zst" }, "armv7-unknown-linux-gnueabi": { - "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/SHA256SUMS", - "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/cpython-3.11.15+20260805-armv7-unknown-linux-gnueabi-lto-full.tar.zst" + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/cpython-3.11.15+20260807-armv7-unknown-linux-gnueabi-lto-full.tar.zst" }, "armv7-unknown-linux-gnueabihf": { - "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/SHA256SUMS", - "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/cpython-3.11.15+20260805-armv7-unknown-linux-gnueabihf-lto-full.tar.zst" + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/cpython-3.11.15+20260807-armv7-unknown-linux-gnueabihf-lto-full.tar.zst" }, "i686-pc-windows-msvc": { - "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/SHA256SUMS", - "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/cpython-3.11.15+20260805-i686-pc-windows-msvc-pgo-full.tar.zst" + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/cpython-3.11.15+20260807-i686-pc-windows-msvc-pgo-full.tar.zst" }, "powerpc64le-unknown-linux-gnu": { - "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/SHA256SUMS", - "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/cpython-3.11.15+20260805-ppc64le-unknown-linux-gnu-lto-full.tar.zst" + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/cpython-3.11.15+20260807-ppc64le-unknown-linux-gnu-lto-full.tar.zst" }, "riscv64gc-unknown-linux-gnu": { - "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/SHA256SUMS", - "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/cpython-3.11.15+20260805-riscv64-unknown-linux-gnu-lto-full.tar.zst" + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/cpython-3.11.15+20260807-riscv64-unknown-linux-gnu-lto-full.tar.zst" }, "s390x-unknown-linux-gnu": { - "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/SHA256SUMS", - "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/cpython-3.11.15+20260805-s390x-unknown-linux-gnu-lto-full.tar.zst" + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/cpython-3.11.15+20260807-s390x-unknown-linux-gnu-lto-full.tar.zst" }, "x86_64-apple-darwin": { - "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/SHA256SUMS", - "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/cpython-3.11.15+20260805-x86_64-apple-darwin-pgo+lto-full.tar.zst" + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/cpython-3.11.15+20260807-x86_64-apple-darwin-pgo+lto-full.tar.zst" }, "x86_64-pc-windows-msvc": { - "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/SHA256SUMS", - "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/cpython-3.11.15+20260805-x86_64-pc-windows-msvc-pgo-full.tar.zst" + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/cpython-3.11.15+20260807-x86_64-pc-windows-msvc-pgo-full.tar.zst" }, "x86_64-unknown-linux-gnu": { - "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/SHA256SUMS", - "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/cpython-3.11.15+20260805-x86_64-unknown-linux-gnu-pgo+lto-full.tar.zst" + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/cpython-3.11.15+20260807-x86_64-unknown-linux-gnu-pgo+lto-full.tar.zst" }, "x86_64-unknown-linux-musl": { - "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/SHA256SUMS", - "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/cpython-3.11.15+20260805-x86_64-unknown-linux-musl-lto-full.tar.zst" + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/cpython-3.11.15+20260807-x86_64-unknown-linux-musl-lto-full.tar.zst" } }, "3.11.3": { @@ -1653,60 +1653,60 @@ }, "3.12.13": { "aarch64-apple-darwin": { - "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/SHA256SUMS", - "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/cpython-3.12.13+20260805-aarch64-apple-darwin-pgo+lto-full.tar.zst" + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/cpython-3.12.13+20260807-aarch64-apple-darwin-pgo+lto-full.tar.zst" }, "aarch64-pc-windows-msvc": { - "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/SHA256SUMS", - "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/cpython-3.12.13+20260805-aarch64-pc-windows-msvc-pgo-full.tar.zst" + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/cpython-3.12.13+20260807-aarch64-pc-windows-msvc-pgo-full.tar.zst" }, "aarch64-unknown-linux-gnu": { - "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/SHA256SUMS", - "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/cpython-3.12.13+20260805-aarch64-unknown-linux-gnu-pgo+lto-full.tar.zst" + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/cpython-3.12.13+20260807-aarch64-unknown-linux-gnu-pgo+lto-full.tar.zst" }, "aarch64-unknown-linux-musl": { - "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/SHA256SUMS", - "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/cpython-3.12.13+20260805-aarch64-unknown-linux-musl-lto-full.tar.zst" + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/cpython-3.12.13+20260807-aarch64-unknown-linux-musl-lto-full.tar.zst" }, "armv7-unknown-linux-gnueabi": { - "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/SHA256SUMS", - "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/cpython-3.12.13+20260805-armv7-unknown-linux-gnueabi-lto-full.tar.zst" + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/cpython-3.12.13+20260807-armv7-unknown-linux-gnueabi-lto-full.tar.zst" }, "armv7-unknown-linux-gnueabihf": { - "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/SHA256SUMS", - "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/cpython-3.12.13+20260805-armv7-unknown-linux-gnueabihf-lto-full.tar.zst" + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/cpython-3.12.13+20260807-armv7-unknown-linux-gnueabihf-lto-full.tar.zst" }, "i686-pc-windows-msvc": { - "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/SHA256SUMS", - "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/cpython-3.12.13+20260805-i686-pc-windows-msvc-pgo-full.tar.zst" + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/cpython-3.12.13+20260807-i686-pc-windows-msvc-pgo-full.tar.zst" }, "powerpc64le-unknown-linux-gnu": { - "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/SHA256SUMS", - "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/cpython-3.12.13+20260805-ppc64le-unknown-linux-gnu-lto-full.tar.zst" + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/cpython-3.12.13+20260807-ppc64le-unknown-linux-gnu-lto-full.tar.zst" }, "riscv64gc-unknown-linux-gnu": { - "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/SHA256SUMS", - "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/cpython-3.12.13+20260805-riscv64-unknown-linux-gnu-lto-full.tar.zst" + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/cpython-3.12.13+20260807-riscv64-unknown-linux-gnu-lto-full.tar.zst" }, "s390x-unknown-linux-gnu": { - "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/SHA256SUMS", - "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/cpython-3.12.13+20260805-s390x-unknown-linux-gnu-lto-full.tar.zst" + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/cpython-3.12.13+20260807-s390x-unknown-linux-gnu-lto-full.tar.zst" }, "x86_64-apple-darwin": { - "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/SHA256SUMS", - "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/cpython-3.12.13+20260805-x86_64-apple-darwin-pgo+lto-full.tar.zst" + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/cpython-3.12.13+20260807-x86_64-apple-darwin-pgo+lto-full.tar.zst" }, "x86_64-pc-windows-msvc": { - "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/SHA256SUMS", - "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/cpython-3.12.13+20260805-x86_64-pc-windows-msvc-pgo-full.tar.zst" + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/cpython-3.12.13+20260807-x86_64-pc-windows-msvc-pgo-full.tar.zst" }, "x86_64-unknown-linux-gnu": { - "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/SHA256SUMS", - "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/cpython-3.12.13+20260805-x86_64-unknown-linux-gnu-pgo+lto-full.tar.zst" + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/cpython-3.12.13+20260807-x86_64-unknown-linux-gnu-pgo+lto-full.tar.zst" }, "x86_64-unknown-linux-musl": { - "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/SHA256SUMS", - "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/cpython-3.12.13+20260805-x86_64-unknown-linux-musl-lto-full.tar.zst" + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/cpython-3.12.13+20260807-x86_64-unknown-linux-musl-lto-full.tar.zst" } }, "3.12.2": { @@ -2555,6 +2555,64 @@ "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/cpython-3.13.14+20260805-x86_64-unknown-linux-musl-lto-full.tar.zst" } }, + "3.13.15": { + "aarch64-apple-darwin": { + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/cpython-3.13.15+20260807-aarch64-apple-darwin-pgo+lto-full.tar.zst" + }, + "aarch64-pc-windows-msvc": { + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/cpython-3.13.15+20260807-aarch64-pc-windows-msvc-pgo-full.tar.zst" + }, + "aarch64-unknown-linux-gnu": { + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/cpython-3.13.15+20260807-aarch64-unknown-linux-gnu-pgo+lto-full.tar.zst" + }, + "aarch64-unknown-linux-musl": { + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/cpython-3.13.15+20260807-aarch64-unknown-linux-musl-lto-full.tar.zst" + }, + "armv7-unknown-linux-gnueabi": { + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/cpython-3.13.15+20260807-armv7-unknown-linux-gnueabi-lto-full.tar.zst" + }, + "armv7-unknown-linux-gnueabihf": { + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/cpython-3.13.15+20260807-armv7-unknown-linux-gnueabihf-lto-full.tar.zst" + }, + "i686-pc-windows-msvc": { + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/cpython-3.13.15+20260807-i686-pc-windows-msvc-pgo-full.tar.zst" + }, + "powerpc64le-unknown-linux-gnu": { + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/cpython-3.13.15+20260807-ppc64le-unknown-linux-gnu-lto-full.tar.zst" + }, + "riscv64gc-unknown-linux-gnu": { + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/cpython-3.13.15+20260807-riscv64-unknown-linux-gnu-lto-full.tar.zst" + }, + "s390x-unknown-linux-gnu": { + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/cpython-3.13.15+20260807-s390x-unknown-linux-gnu-lto-full.tar.zst" + }, + "x86_64-apple-darwin": { + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/cpython-3.13.15+20260807-x86_64-apple-darwin-pgo+lto-full.tar.zst" + }, + "x86_64-pc-windows-msvc": { + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/cpython-3.13.15+20260807-x86_64-pc-windows-msvc-pgo-full.tar.zst" + }, + "x86_64-unknown-linux-gnu": { + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/cpython-3.13.15+20260807-x86_64-unknown-linux-gnu-pgo+lto-full.tar.zst" + }, + "x86_64-unknown-linux-musl": { + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/cpython-3.13.15+20260807-x86_64-unknown-linux-musl-lto-full.tar.zst" + } + }, "3.13.2": { "aarch64-apple-darwin": { "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20250317/cpython-3.13.2+20250317-aarch64-apple-darwin-pgo+lto-full.tar.zst.sha256", @@ -4053,60 +4111,60 @@ }, "3.14.7": { "aarch64-apple-darwin": { - "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/SHA256SUMS", - "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/cpython-3.14.7+20260805-aarch64-apple-darwin-pgo+lto-full.tar.zst" + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/cpython-3.14.7+20260807-aarch64-apple-darwin-pgo+lto-full.tar.zst" }, "aarch64-pc-windows-msvc": { - "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/SHA256SUMS", - "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/cpython-3.14.7+20260805-aarch64-pc-windows-msvc-pgo-full.tar.zst" + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/cpython-3.14.7+20260807-aarch64-pc-windows-msvc-pgo-full.tar.zst" }, "aarch64-unknown-linux-gnu": { - "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/SHA256SUMS", - "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/cpython-3.14.7+20260805-aarch64-unknown-linux-gnu-pgo+lto-full.tar.zst" + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/cpython-3.14.7+20260807-aarch64-unknown-linux-gnu-pgo+lto-full.tar.zst" }, "aarch64-unknown-linux-musl": { - "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/SHA256SUMS", - "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/cpython-3.14.7+20260805-aarch64-unknown-linux-musl-lto-full.tar.zst" + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/cpython-3.14.7+20260807-aarch64-unknown-linux-musl-lto-full.tar.zst" }, "armv7-unknown-linux-gnueabi": { - "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/SHA256SUMS", - "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/cpython-3.14.7+20260805-armv7-unknown-linux-gnueabi-lto-full.tar.zst" + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/cpython-3.14.7+20260807-armv7-unknown-linux-gnueabi-lto-full.tar.zst" }, "armv7-unknown-linux-gnueabihf": { - "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/SHA256SUMS", - "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/cpython-3.14.7+20260805-armv7-unknown-linux-gnueabihf-lto-full.tar.zst" + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/cpython-3.14.7+20260807-armv7-unknown-linux-gnueabihf-lto-full.tar.zst" }, "i686-pc-windows-msvc": { - "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/SHA256SUMS", - "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/cpython-3.14.7+20260805-i686-pc-windows-msvc-pgo-full.tar.zst" + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/cpython-3.14.7+20260807-i686-pc-windows-msvc-pgo-full.tar.zst" }, "powerpc64le-unknown-linux-gnu": { - "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/SHA256SUMS", - "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/cpython-3.14.7+20260805-ppc64le-unknown-linux-gnu-lto-full.tar.zst" + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/cpython-3.14.7+20260807-ppc64le-unknown-linux-gnu-lto-full.tar.zst" }, "riscv64gc-unknown-linux-gnu": { - "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/SHA256SUMS", - "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/cpython-3.14.7+20260805-riscv64-unknown-linux-gnu-lto-full.tar.zst" + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/cpython-3.14.7+20260807-riscv64-unknown-linux-gnu-lto-full.tar.zst" }, "s390x-unknown-linux-gnu": { - "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/SHA256SUMS", - "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/cpython-3.14.7+20260805-s390x-unknown-linux-gnu-lto-full.tar.zst" + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/cpython-3.14.7+20260807-s390x-unknown-linux-gnu-lto-full.tar.zst" }, "x86_64-apple-darwin": { - "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/SHA256SUMS", - "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/cpython-3.14.7+20260805-x86_64-apple-darwin-pgo+lto-full.tar.zst" + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/cpython-3.14.7+20260807-x86_64-apple-darwin-pgo+lto-full.tar.zst" }, "x86_64-pc-windows-msvc": { - "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/SHA256SUMS", - "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/cpython-3.14.7+20260805-x86_64-pc-windows-msvc-pgo-full.tar.zst" + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/cpython-3.14.7+20260807-x86_64-pc-windows-msvc-pgo-full.tar.zst" }, "x86_64-unknown-linux-gnu": { - "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/SHA256SUMS", - "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/cpython-3.14.7+20260805-x86_64-unknown-linux-gnu-pgo+lto-full.tar.zst" + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/cpython-3.14.7+20260807-x86_64-unknown-linux-gnu-pgo+lto-full.tar.zst" }, "x86_64-unknown-linux-musl": { - "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/SHA256SUMS", - "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/cpython-3.14.7+20260805-x86_64-unknown-linux-musl-lto-full.tar.zst" + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/cpython-3.14.7+20260807-x86_64-unknown-linux-musl-lto-full.tar.zst" } }, "3.15.0-a.1": { @@ -4807,60 +4865,60 @@ }, "3.15.0-rc.1": { "aarch64-apple-darwin": { - "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/SHA256SUMS", - "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/cpython-3.15.0rc1+20260805-aarch64-apple-darwin-pgo+lto-full.tar.zst" + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/cpython-3.15.0rc1+20260807-aarch64-apple-darwin-pgo+lto-full.tar.zst" }, "aarch64-pc-windows-msvc": { - "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/SHA256SUMS", - "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/cpython-3.15.0rc1+20260805-aarch64-pc-windows-msvc-pgo-full.tar.zst" + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/cpython-3.15.0rc1+20260807-aarch64-pc-windows-msvc-pgo-full.tar.zst" }, "aarch64-unknown-linux-gnu": { - "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/SHA256SUMS", - "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/cpython-3.15.0rc1+20260805-aarch64-unknown-linux-gnu-pgo+lto-full.tar.zst" + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/cpython-3.15.0rc1+20260807-aarch64-unknown-linux-gnu-pgo+lto-full.tar.zst" }, "aarch64-unknown-linux-musl": { - "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/SHA256SUMS", - "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/cpython-3.15.0rc1+20260805-aarch64-unknown-linux-musl-lto-full.tar.zst" + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/cpython-3.15.0rc1+20260807-aarch64-unknown-linux-musl-lto-full.tar.zst" }, "armv7-unknown-linux-gnueabi": { - "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/SHA256SUMS", - "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/cpython-3.15.0rc1+20260805-armv7-unknown-linux-gnueabi-lto-full.tar.zst" + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/cpython-3.15.0rc1+20260807-armv7-unknown-linux-gnueabi-lto-full.tar.zst" }, "armv7-unknown-linux-gnueabihf": { - "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/SHA256SUMS", - "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/cpython-3.15.0rc1+20260805-armv7-unknown-linux-gnueabihf-lto-full.tar.zst" + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/cpython-3.15.0rc1+20260807-armv7-unknown-linux-gnueabihf-lto-full.tar.zst" }, "i686-pc-windows-msvc": { - "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/SHA256SUMS", - "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/cpython-3.15.0rc1+20260805-i686-pc-windows-msvc-pgo-full.tar.zst" + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/cpython-3.15.0rc1+20260807-i686-pc-windows-msvc-pgo-full.tar.zst" }, "powerpc64le-unknown-linux-gnu": { - "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/SHA256SUMS", - "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/cpython-3.15.0rc1+20260805-ppc64le-unknown-linux-gnu-lto-full.tar.zst" + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/cpython-3.15.0rc1+20260807-ppc64le-unknown-linux-gnu-lto-full.tar.zst" }, "riscv64gc-unknown-linux-gnu": { - "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/SHA256SUMS", - "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/cpython-3.15.0rc1+20260805-riscv64-unknown-linux-gnu-lto-full.tar.zst" + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/cpython-3.15.0rc1+20260807-riscv64-unknown-linux-gnu-lto-full.tar.zst" }, "s390x-unknown-linux-gnu": { - "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/SHA256SUMS", - "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/cpython-3.15.0rc1+20260805-s390x-unknown-linux-gnu-lto-full.tar.zst" + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/cpython-3.15.0rc1+20260807-s390x-unknown-linux-gnu-lto-full.tar.zst" }, "x86_64-apple-darwin": { - "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/SHA256SUMS", - "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/cpython-3.15.0rc1+20260805-x86_64-apple-darwin-pgo+lto-full.tar.zst" + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/cpython-3.15.0rc1+20260807-x86_64-apple-darwin-pgo+lto-full.tar.zst" }, "x86_64-pc-windows-msvc": { - "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/SHA256SUMS", - "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/cpython-3.15.0rc1+20260805-x86_64-pc-windows-msvc-pgo-full.tar.zst" + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/cpython-3.15.0rc1+20260807-x86_64-pc-windows-msvc-pgo-full.tar.zst" }, "x86_64-unknown-linux-gnu": { - "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/SHA256SUMS", - "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/cpython-3.15.0rc1+20260805-x86_64-unknown-linux-gnu-pgo+lto-full.tar.zst" + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/cpython-3.15.0rc1+20260807-x86_64-unknown-linux-gnu-pgo+lto-full.tar.zst" }, "x86_64-unknown-linux-musl": { - "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/SHA256SUMS", - "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260805/cpython-3.15.0rc1+20260805-x86_64-unknown-linux-musl-lto-full.tar.zst" + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/cpython-3.15.0rc1+20260807-x86_64-unknown-linux-musl-lto-full.tar.zst" } }, "3.7.6": { From c4267cc3093213849f38c407d7fdb2dec84f8d4c Mon Sep 17 00:00:00 2001 From: Miles Johnson Date: Sat, 8 Aug 2026 13:59:23 -0700 Subject: [PATCH 35/78] new: Node and package manager improvements. (#177) --- .../src/package_json.rs | 7 +- toolchains/node-depman/src/tier1.rs | 24 +- tools/node-depman/CHANGELOG.md | 13 +- tools/node-depman/src/package_manager.rs | 64 +++- tools/node-depman/src/proto.rs | 116 +++--- tools/node-depman/tests/activate_test.rs | 46 +++ tools/node-depman/tests/download_test.rs | 344 ++++++++++++++++++ tools/node-depman/tests/metadata_test.rs | 4 +- tools/node-depman/tests/versions_test.rs | 56 +++ tools/node/CHANGELOG.md | 6 + tools/node/src/proto.rs | 34 +- tools/node/tests/versions_test.rs | 55 +++ 12 files changed, 697 insertions(+), 72 deletions(-) diff --git a/crates/lang-javascript-common/src/package_json.rs b/crates/lang-javascript-common/src/package_json.rs index a5795bdf..5508a994 100644 --- a/crates/lang-javascript-common/src/package_json.rs +++ b/crates/lang-javascript-common/src/package_json.rs @@ -75,10 +75,7 @@ pub fn extract_engine_version(package_json: &PackageJson, key: &str) -> Option( - package_json: &'a PackageJson, - key: &str, -) -> Option<&'a str> { +pub fn extract_package_manager_version(package_json: &PackageJson, key: &str) -> Option { if let Some(pm) = &package_json.package_manager { let mut parts = pm.split('@'); let name = parts.next().unwrap_or_default(); @@ -95,7 +92,7 @@ pub fn extract_package_manager_version<'a>( "latest" }; - return Some(value); + return Some(value.into()); } } diff --git a/toolchains/node-depman/src/tier1.rs b/toolchains/node-depman/src/tier1.rs index 99b4db67..1f023b62 100644 --- a/toolchains/node-depman/src/tier1.rs +++ b/toolchains/node-depman/src/tier1.rs @@ -21,16 +21,18 @@ pub fn register_toolchain( lock_file_names: vec!["package-lock.json".into(), "npm-shrinkwrap.json".into()], ..Default::default() }, - PackageManager::Pnpm | PackageManager::Pnpm11 => RegisterToolchainOutput { - config_file_globs: vec![ - ".npmrc".into(), - "pnpm-workspace.yaml".into(), - ".pnpmfile.*".into(), - ], - exe_names: vec!["pnpm".into(), "pnpx".into()], - lock_file_names: vec!["pnpm-lock.yaml".into()], - ..Default::default() - }, + PackageManager::Pnpm | PackageManager::Pnpm11 | PackageManager::Pnpm12 => { + RegisterToolchainOutput { + config_file_globs: vec![ + ".npmrc".into(), + "pnpm-workspace.yaml".into(), + ".pnpmfile.*".into(), + ], + exe_names: vec!["pnpm".into(), "pnpx".into()], + lock_file_names: vec!["pnpm-lock.yaml".into()], + ..Default::default() + } + } PackageManager::Yarn1 | PackageManager::Yarn2to5 | PackageManager::Yarn6 => { RegisterToolchainOutput { config_file_globs: vec![".npmrc".into(), ".yarnrc.*".into()], @@ -70,7 +72,7 @@ pub fn define_toolchain_config() -> FnResult> Ok(Json(DefineToolchainConfigOutput { schema: match manager { PackageManager::Npm => SchemaBuilder::build_root::(), - PackageManager::Pnpm | PackageManager::Pnpm11 => { + PackageManager::Pnpm | PackageManager::Pnpm11 | PackageManager::Pnpm12 => { SchemaBuilder::build_root::() } PackageManager::Yarn1 | PackageManager::Yarn2to5 | PackageManager::Yarn6 => { diff --git a/tools/node-depman/CHANGELOG.md b/tools/node-depman/CHANGELOG.md index e3e8ec86..6e9a4522 100644 --- a/tools/node-depman/CHANGELOG.md +++ b/tools/node-depman/CHANGELOG.md @@ -1,5 +1,16 @@ # Changelog +## Unreleased + +#### 🚀 Updates + +- Added experimental support for pnpm v12 (Rust based). +- Updated version detection to loop through all possible fields (`packageManager`, `engines`, etc) to find an applicable version, instead of failing on the first invalid version. + +#### 🐞 Fixes + +- Fixed an issue where auth headers were not included in load verion requests, which would cause failures for private registries. + ## 0.19.1 #### 🚀 Updates @@ -11,7 +22,7 @@ #### 🚀 Updates - Updated to support proto v0.59 release. -- Added experimental support for yarn v6. +- Added experimental support for yarn v6 (Rust based). ## 0.18.1 diff --git a/tools/node-depman/src/package_manager.rs b/tools/node-depman/src/package_manager.rs index 84f3a274..7840d92a 100644 --- a/tools/node-depman/src/package_manager.rs +++ b/tools/node-depman/src/package_manager.rs @@ -5,7 +5,10 @@ use crate::yarn_compat::*; use npmrc_config_rs::{ Credentials, LoadOptions, NpmrcConfig, nerf_dart, registry::parse_registry_url, }; -use proto_pdk::{AnyResult, VersionSpec, VirtualPath, get_plugin_id}; +use proto_pdk::{ + AnyResult, HostArch, HostEnvironment, HostLibc, HostOS, PluginError, VersionSpec, VirtualPath, + get_plugin_id, +}; use rustc_hash::FxHashMap; use starbase_utils::{fs::find_upwards, yaml}; @@ -14,10 +17,15 @@ pub enum PackageManager { Npm, Pnpm, + // Major changes Pnpm11, + // Rust based + Pnpm12, Yarn1, + // Major rewrite with different APIs / PNP Yarn2to5, + // Rust based Yarn6, } @@ -39,16 +47,20 @@ impl PackageManager { if manager == Self::Pnpm { manager = match version { - VersionSpec::Canary => Self::Pnpm11, + VersionSpec::Canary => Self::Pnpm12, VersionSpec::Alias(alias) => { if alias == "latest" { Self::Pnpm11 + } else if alias == "next" { + Self::Pnpm12 } else { Self::Pnpm } } VersionSpec::Version(version) => { - if version.major >= 11 { + if version.major >= 12 { + Self::Pnpm12 + } else if version.major >= 11 { Self::Pnpm11 } else { Self::Pnpm @@ -87,7 +99,7 @@ impl PackageManager { } pub fn is_pnpm(&self) -> bool { - matches!(self, Self::Pnpm | Self::Pnpm11) + matches!(self, Self::Pnpm | Self::Pnpm11 | Self::Pnpm12) } pub fn is_yarn(&self) -> bool { @@ -97,7 +109,7 @@ impl PackageManager { pub fn get_bin_name(&self) -> String { match self { Self::Npm => "npm".into(), - Self::Pnpm | Self::Pnpm11 => "pnpm".into(), + Self::Pnpm | Self::Pnpm11 | Self::Pnpm12 => "pnpm".into(), Self::Yarn1 | Self::Yarn2to5 | Self::Yarn6 => "yarn".into(), } } @@ -109,6 +121,46 @@ impl PackageManager { } } + pub fn get_package_name_for_download(&self, env: &HostEnvironment) -> AnyResult { + match self { + Self::Pnpm12 => { + let arch = match env.arch { + HostArch::Arm64 => "arm64", + HostArch::X64 => "x64", + other => { + return Err(PluginError::UnsupportedArch { + tool: "pnpm".into(), + arch: other.to_string(), + } + .into()); + } + }; + + let os = match env.os { + HostOS::MacOS => "darwin", + HostOS::Linux => "linux", + HostOS::Windows => "windows", + other => { + return Err(PluginError::UnsupportedOS { + tool: "pnpm".into(), + os: other.to_string(), + } + .into()); + } + }; + + let mut name = format!("@pnpm/exe.{os}-{arch}"); + + if env.libc == HostLibc::Musl { + name.push_str("-musl"); + } + + Ok(name) + } + _ => Ok(self.get_package_name()), + } + } + pub fn get_http_headers( &self, registry_url: &str, @@ -118,7 +170,7 @@ impl PackageManager { let url = parse_registry_url(registry_url)?; let credentials = match self { - Self::Npm | Self::Pnpm | Self::Pnpm11 => { + Self::Npm | Self::Pnpm | Self::Pnpm11 | Self::Pnpm12 => { let rc = NpmrcConfig::load_with_options(LoadOptions { cwd: Some(working_dir.into()), global_prefix: None, diff --git a/tools/node-depman/src/proto.rs b/tools/node-depman/src/proto.rs index 96576b54..6f6c95a8 100644 --- a/tools/node-depman/src/proto.rs +++ b/tools/node-depman/src/proto.rs @@ -33,10 +33,11 @@ pub fn register_tool(Json(_): Json) -> FnResult(&input.content) { let manager_name = PackageManager::detect()?.get_bin_name(); + let mut candidates = vec![]; if let Some(constraint) = extract_dev_engine_package_manager_version(&package_json, &manager_name) { - version = Some(UnresolvedVersionSpec::parse(constraint)?); + candidates.push(constraint); } - if version.is_none() - && let Some(constraint) = extract_package_manager_version(&package_json, &manager_name) - { - version = Some(UnresolvedVersionSpec::parse(constraint)?); + if let Some(constraint) = extract_package_manager_version(&package_json, &manager_name) { + candidates.push(constraint); } - if version.is_none() - && let Some(constraint) = - extract_volta_version(&package_json, &input.path, &manager_name)? + if let Some(constraint) = extract_volta_version(&package_json, &input.path, &manager_name)? { - version = Some(UnresolvedVersionSpec::parse(constraint)?); + candidates.push(constraint); + } + + if let Some(constraint) = extract_engine_version(&package_json, &manager_name) { + candidates.push(constraint); + } + + let mut error = None; + + for candidates in candidates { + match UnresolvedVersionSpec::parse(candidates) { + Ok(spec) => { + version = Some(spec); + break; + } + Err(err) => { + if error.is_none() { + error = Some(err); + } + } + }; } if version.is_none() - && let Some(constraint) = extract_engine_version(&package_json, &manager_name) + && let Some(error) = error { - version = Some(UnresolvedVersionSpec::parse(constraint)?); + return Err(plugin_err!("{error}")); } } @@ -156,14 +174,22 @@ pub fn unpin_version(Json(input): Json) -> FnResult) -> FnResult> { +pub fn load_versions(Json(input): Json) -> FnResult> { let mut output = LoadVersionsOutput::default(); let manager = PackageManager::detect()?; let registry_url = get_tool_config::()?.registry_url; let package_name = manager.get_package_name(); - - let mut map_output = |res_text: String, is_yarn: bool| -> Result<(), Error> { - let res = parse_registry_response(res_text, is_yarn)?; + let headers = manager.get_http_headers(®istry_url, &input.context.working_dir)?; + + let mut fetch_versions = |url: String, is_yarn: bool| -> Result<(), Error> { + let res = parse_registry_response( + fetch(SendRequestInput { + url, + headers: headers.clone(), + })? + .text()?, + is_yarn, + )?; for item in res.versions.values() { output.versions.push(VersionSpec::parse(&item.version)?); @@ -192,13 +218,10 @@ pub fn load_versions(Json(_input): Json) -> FnResult) -> FnResult "aarch64", HostArch::X64 => "x86_64", @@ -378,7 +398,7 @@ pub fn download_prebuilt( } // Everything else is provided by the npm registry - let mut package_name = manager.get_package_name(); + let mut package_name = manager.get_package_name_for_download(env)?; // Version 2.4.3 was published to the wrong package. It should // have been published to `@yarnpkg/cli-dist` but was published @@ -456,15 +476,29 @@ pub fn locate_executables( // https://github.com/npm/cli/blob/latest/workspaces/config/lib/index.js#L339 globals_lookup_dirs.push("$TOOL_DIR/shims".into()); } - PackageManager::Pnpm | PackageManager::Pnpm11 => { - primary = ExecutableConfig::new_primary("shims/pnpm"); + PackageManager::Pnpm | PackageManager::Pnpm11 | PackageManager::Pnpm12 => { + if manager == PackageManager::Pnpm12 { + let exe_name = env.os.get_exe_name("pnpm"); + + primary = ExecutableConfig::new_primary(&exe_name); + secondary.insert("pn".into(), ExecutableConfig::new(&exe_name)); + + let pnpx_config = ExecutableConfig::new(exe_name) + .no_bin(true) + .shim_before_args(StringOrVec::String("dlx".into())); - // pnpx - secondary.insert("pnpx".into(), ExecutableConfig::new("shims/pnpx")); + secondary.insert("pnpx".into(), pnpx_config.clone()); + secondary.insert("pnx".into(), pnpx_config); + } else { + primary = ExecutableConfig::new_primary("shims/pnpm"); + + // pnpx + secondary.insert("pnpx".into(), ExecutableConfig::new("shims/pnpx")); - if manager == PackageManager::Pnpm11 { - secondary.insert("pn".into(), ExecutableConfig::new("shims/pn")); - secondary.insert("pnx".into(), ExecutableConfig::new("shims/pnx")); + if manager == PackageManager::Pnpm11 { + secondary.insert("pn".into(), ExecutableConfig::new("shims/pn")); + secondary.insert("pnx".into(), ExecutableConfig::new("shims/pnx")); + } } // https://pnpm.io/npmrc#global-dir @@ -573,7 +607,7 @@ pub fn activate_environment( // Pnpm has explicit support for the bin and root dirs, // which makes this super simple to handle. - PackageManager::Pnpm | PackageManager::Pnpm11 => { + PackageManager::Pnpm | PackageManager::Pnpm11 | PackageManager::Pnpm12 => { output .env .insert("pnpm_config_global_dir".into(), globals_root_dir); @@ -636,8 +670,8 @@ fn create_internal_shims( PackageManager::Yarn1 | PackageManager::Yarn2to5 => { create_internal_shim(env, tool_dir, "yarn", "yarn.js")?; } - // Yarn v6+ is a native binary and requires no shims - PackageManager::Yarn6 => {} + // Yarn v6+ and pnpm v12+ is a native binary and requires no shims + PackageManager::Yarn6 | PackageManager::Pnpm12 => {} }; Ok(()) diff --git a/tools/node-depman/tests/activate_test.rs b/tools/node-depman/tests/activate_test.rs index 2d8fe371..fff430ce 100644 --- a/tools/node-depman/tests/activate_test.rs +++ b/tools/node-depman/tests/activate_test.rs @@ -166,6 +166,52 @@ mod node_depman_tool { ]) ); } + + #[tokio::test(flavor = "multi_thread")] + + async fn adds_env_var_for_v12() { + let sandbox = create_empty_proto_sandbox(); + let plugin = sandbox + .create_plugin_with_config("pnpm-test", |config| { + config.tool_config(NodeDepmanPluginConfig { + shared_globals_dir: true, + }); + }) + .await; + + let result = plugin + .activate_environment(ActivateEnvironmentInput { + context: PluginContext { + version: VersionSpec::parse("12.0.0").unwrap(), + ..Default::default() + }, + globals_dir: Some(create_globals_dir()), + ..Default::default() + }) + .await; + + assert_eq!( + result.env, + HashMap::from_iter([ + ( + "pnpm_config_global_dir".into(), + sandbox + .path() + .join(".proto/tools/node/globals") + .to_string_lossy() + .to_string() + ), + ( + "pnpm_config_global_bin_dir".into(), + sandbox + .path() + .join(".proto/tools/node/globals/bin") + .to_string_lossy() + .to_string() + ) + ]) + ); + } } mod yarn { diff --git a/tools/node-depman/tests/download_test.rs b/tools/node-depman/tests/download_test.rs index bf383a41..238d7bf3 100644 --- a/tools/node-depman/tests/download_test.rs +++ b/tools/node-depman/tests/download_test.rs @@ -205,6 +205,34 @@ mod node_depman_tool { ); } + #[tokio::test(flavor = "multi_thread")] + async fn supports_prebuilt_v11() { + let sandbox = create_empty_proto_sandbox(); + let plugin = sandbox + .create_plugin_with_config("pnpm-test", |config| { + config.host(HostOS::MacOS, HostArch::Arm64); + }) + .await; + + // v11 is still a platform agnostic tarball, not a native binary + assert_eq!( + plugin + .download_prebuilt(DownloadPrebuiltInput { + context: PluginContext { + version: VersionSpec::parse("11.0.0").unwrap(), + ..Default::default() + }, + ..Default::default() + }) + .await, + DownloadPrebuiltOutput { + archive_prefix: Some("package".into()), + download_url: "https://registry.npmjs.org/pnpm/-/pnpm-11.0.0.tgz".into(), + ..Default::default() + } + ); + } + #[tokio::test(flavor = "multi_thread")] async fn locates_default_bin() { let sandbox = create_empty_proto_sandbox(); @@ -336,6 +364,322 @@ mod node_depman_tool { } } + mod pnpm12 { + use super::*; + + // Pnpm >= 12 is Rust based and downloaded from the npm registry + // as an os/arch specific package: @pnpm/exe.{os}-{arch} + + #[tokio::test(flavor = "multi_thread")] + async fn supports_prebuilt_macos_arm64() { + let sandbox = create_empty_proto_sandbox(); + let plugin = sandbox + .create_plugin_with_config("pnpm-test", |config| { + config.host(HostOS::MacOS, HostArch::Arm64); + }) + .await; + + assert_eq!( + plugin + .download_prebuilt(DownloadPrebuiltInput { + context: PluginContext { + version: VersionSpec::parse("12.0.0").unwrap(), + ..Default::default() + }, + ..Default::default() + }) + .await, + DownloadPrebuiltOutput { + archive_prefix: Some("package".into()), + download_url: "https://registry.npmjs.org/@pnpm/exe.darwin-arm64/-/exe.darwin-arm64-12.0.0.tgz".into(), + ..Default::default() + } + ); + } + + #[tokio::test(flavor = "multi_thread")] + async fn supports_prebuilt_linux_x64_gnu() { + let sandbox = create_empty_proto_sandbox(); + let plugin = sandbox + .create_plugin_with_config("pnpm-test", |config| { + config.host_with(|host| { + host.os = HostOS::Linux; + host.arch = HostArch::X64; + host.libc = HostLibc::Gnu; + }); + }) + .await; + + // Unlike yarn, gnu is supported and has no libc suffix + assert_eq!( + plugin + .download_prebuilt(DownloadPrebuiltInput { + context: PluginContext { + version: VersionSpec::parse("12.0.0").unwrap(), + ..Default::default() + }, + ..Default::default() + }) + .await, + DownloadPrebuiltOutput { + archive_prefix: Some("package".into()), + download_url: "https://registry.npmjs.org/@pnpm/exe.linux-x64/-/exe.linux-x64-12.0.0.tgz".into(), + ..Default::default() + } + ); + } + + #[tokio::test(flavor = "multi_thread")] + async fn supports_prebuilt_linux_arm64_musl() { + let sandbox = create_empty_proto_sandbox(); + let plugin = sandbox + .create_plugin_with_config("pnpm-test", |config| { + config.host_with(|host| { + host.os = HostOS::Linux; + host.arch = HostArch::Arm64; + host.libc = HostLibc::Musl; + }); + }) + .await; + + assert_eq!( + plugin + .download_prebuilt(DownloadPrebuiltInput { + context: PluginContext { + version: VersionSpec::parse("12.0.0").unwrap(), + ..Default::default() + }, + ..Default::default() + }) + .await, + DownloadPrebuiltOutput { + archive_prefix: Some("package".into()), + download_url: "https://registry.npmjs.org/@pnpm/exe.linux-arm64-musl/-/exe.linux-arm64-musl-12.0.0.tgz".into(), + ..Default::default() + } + ); + } + + #[tokio::test(flavor = "multi_thread")] + async fn supports_prebuilt_windows_x64() { + let sandbox = create_empty_proto_sandbox(); + let plugin = sandbox + .create_plugin_with_config("pnpm-test", |config| { + config.host(HostOS::Windows, HostArch::X64); + }) + .await; + + // Unlike yarn, windows is supported + assert_eq!( + plugin + .download_prebuilt(DownloadPrebuiltInput { + context: PluginContext { + version: VersionSpec::parse("12.0.0").unwrap(), + ..Default::default() + }, + ..Default::default() + }) + .await, + DownloadPrebuiltOutput { + archive_prefix: Some("package".into()), + download_url: "https://registry.npmjs.org/@pnpm/exe.windows-x64/-/exe.windows-x64-12.0.0.tgz".into(), + ..Default::default() + } + ); + } + + #[tokio::test(flavor = "multi_thread")] + async fn extracts_auth_token_header() { + let sandbox = create_empty_proto_sandbox(); + sandbox.create_file(".npmrc", "//registry.npmjs.org/:_authToken = abc123"); + + let plugin = sandbox + .create_plugin_with_config("pnpm-test", |config| { + config.host(HostOS::Linux, HostArch::Arm64); + }) + .await; + + assert_eq!( + plugin + .download_prebuilt(DownloadPrebuiltInput { + context: PluginContext { + version: VersionSpec::parse("12.0.0").unwrap(), + working_dir: plugin.tool.to_virtual_path(sandbox.path()), + ..Default::default() + }, + ..Default::default() + }) + .await, + DownloadPrebuiltOutput { + archive_prefix: Some("package".into()), + download_url: "https://registry.npmjs.org/@pnpm/exe.linux-arm64/-/exe.linux-arm64-12.0.0.tgz".into(), + http_headers: FxHashMap::from_iter([( + "Authorization".into(), + "Bearer abc123".into() + )]), + ..Default::default() + } + ); + } + + #[tokio::test(flavor = "multi_thread")] + #[should_panic(expected = "unsupported architecture x86.")] + async fn doesnt_support_other_archs() { + let sandbox = create_empty_proto_sandbox(); + let plugin = sandbox + .create_plugin_with_config("pnpm-test", |config| { + config.host(HostOS::Linux, HostArch::X86); + }) + .await; + + plugin + .download_prebuilt(DownloadPrebuiltInput { + context: PluginContext { + version: VersionSpec::parse("12.0.0").unwrap(), + ..Default::default() + }, + ..Default::default() + }) + .await; + } + + #[tokio::test(flavor = "multi_thread")] + #[should_panic(expected = "unsupported OS freebsd.")] + async fn doesnt_support_other_os() { + let sandbox = create_empty_proto_sandbox(); + let plugin = sandbox + .create_plugin_with_config("pnpm-test", |config| { + config.host(HostOS::FreeBSD, HostArch::Arm64); + }) + .await; + + plugin + .download_prebuilt(DownloadPrebuiltInput { + context: PluginContext { + version: VersionSpec::parse("12.0.0").unwrap(), + ..Default::default() + }, + ..Default::default() + }) + .await; + } + + #[tokio::test(flavor = "multi_thread")] + #[should_panic(expected = "pnpm does not support canary/nightly versions.")] + async fn doesnt_support_canary() { + let sandbox = create_empty_proto_sandbox(); + let plugin = sandbox + .create_plugin_with_config("pnpm-test", |config| { + config.host(HostOS::MacOS, HostArch::Arm64); + }) + .await; + + plugin + .download_prebuilt(DownloadPrebuiltInput { + context: PluginContext { + version: VersionSpec::parse("canary").unwrap(), + ..Default::default() + }, + ..Default::default() + }) + .await; + } + + #[tokio::test(flavor = "multi_thread")] + async fn locates_native_bin() { + let sandbox = create_empty_proto_sandbox(); + let plugin = sandbox + .create_plugin_with_config("pnpm-test", |config| { + config.host(HostOS::MacOS, HostArch::Arm64); + }) + .await; + + let exes = plugin + .locate_executables(LocateExecutablesInput { + context: PluginContext { + version: VersionSpec::parse("12.0.0").unwrap(), + ..Default::default() + }, + install_dir: plugin.tool.to_virtual_path(sandbox.path()), + }) + .await + .exes; + + assert_eq!(exes.get("pnpm").unwrap().exe_path, Some("pnpm".into())); + assert_eq!(exes.get("pn").unwrap().exe_path, Some("pnpm".into())); + + // pnpx and pnx run through the primary binary as `pnpm dlx` + for alias in ["pnpx", "pnx"] { + let exe = exes.get(alias).unwrap(); + + assert_eq!(exe.exe_path, Some("pnpm".into())); + assert!(exe.no_bin); + assert_eq!( + exe.shim_before_args, + Some(StringOrVec::String("dlx".into())) + ); + } + + // No internal shims are created for the native binary + assert!(!sandbox.path().join("shims/pnpm").exists()); + } + + #[tokio::test(flavor = "multi_thread")] + async fn locates_native_bin_windows() { + let sandbox = create_empty_proto_sandbox(); + let plugin = sandbox + .create_plugin_with_config("pnpm-test", |config| { + config.host(HostOS::Windows, HostArch::X64); + }) + .await; + + let exes = plugin + .locate_executables(LocateExecutablesInput { + context: PluginContext { + version: VersionSpec::parse("12.0.0").unwrap(), + ..Default::default() + }, + install_dir: plugin.tool.to_virtual_path(sandbox.path()), + }) + .await + .exes; + + // The .exe extension must not be rewritten to .cmd + assert_eq!( + exes.get("pnpm").unwrap().exe_path, + Some("pnpm.exe".into()) + ); + } + + #[tokio::test(flavor = "multi_thread")] + async fn locates_native_bin_for_next_alias() { + let sandbox = create_empty_proto_sandbox(); + let plugin = sandbox + .create_plugin_with_config("pnpm-test", |config| { + config.host(HostOS::MacOS, HostArch::Arm64); + }) + .await; + + // The "next" alias maps to v12 + assert_eq!( + plugin + .locate_executables(LocateExecutablesInput { + context: PluginContext { + version: VersionSpec::parse("next").unwrap(), + ..Default::default() + }, + install_dir: plugin.tool.to_virtual_path(sandbox.path()), + }) + .await + .exes + .get("pnpm") + .unwrap() + .exe_path, + Some("pnpm".into()) + ); + } + } + mod yarn1 { use super::*; diff --git a/tools/node-depman/tests/metadata_test.rs b/tools/node-depman/tests/metadata_test.rs index 4c0bcffc..4f265f48 100644 --- a/tools/node-depman/tests/metadata_test.rs +++ b/tools/node-depman/tests/metadata_test.rs @@ -38,7 +38,9 @@ mod node_depman_tool { let metadata = plugin.register_tool(create_metadata("pnpm-test")).await; assert_eq!(metadata.name, "pnpm"); - assert!(metadata.lock_options.ignore_os_arch); + + // v12+ binaries are os/arch specific, so records must be scoped + assert!(!metadata.lock_options.ignore_os_arch); assert_eq!(metadata.type_of, PluginType::DependencyManager); assert_eq!( metadata.plugin_version.unwrap().to_string(), diff --git a/tools/node-depman/tests/versions_test.rs b/tools/node-depman/tests/versions_test.rs index 67248e13..4b819de4 100644 --- a/tools/node-depman/tests/versions_test.rs +++ b/tools/node-depman/tests/versions_test.rs @@ -31,6 +31,62 @@ mod node_depman_tool { ); } + #[tokio::test(flavor = "multi_thread")] + async fn parses_first_field_when_multiple_are_valid() { + let sandbox = create_empty_proto_sandbox(); + let plugin = sandbox.create_plugin("npm-test").await; + + assert_eq!( + plugin + .parse_version_file(ParseVersionFileInput { + content: r#"{ "devEngines": { "packageManager": { "name": "npm", "version": "1.2.3" } }, "packageManager": "npm@4.5.6", "volta": { "npm": "7.8.9" }, "engines": { "npm": "^10" } }"# + .into(), + file: "package.json".into(), + ..Default::default() + }) + .await, + ParseVersionFileOutput { + version: Some(UnresolvedVersionSpec::parse("1.2.3").unwrap()), + } + ); + } + + #[tokio::test(flavor = "multi_thread")] + async fn skips_invalid_field_and_parses_next_valid_field() { + let sandbox = create_empty_proto_sandbox(); + let plugin = sandbox.create_plugin("npm-test").await; + + assert_eq!( + plugin + .parse_version_file(ParseVersionFileInput { + content: r#"{ "packageManager": "npm@https://registry.npmjs.org/npm/-/npm-9.0.0.tgz", "volta": { "npm": "7.8.9" }, "engines": { "npm": "^10" } }"# + .into(), + file: "package.json".into(), + ..Default::default() + }) + .await, + ParseVersionFileOutput { + version: Some(UnresolvedVersionSpec::parse("7.8.9").unwrap()), + } + ); + } + + #[tokio::test(flavor = "multi_thread")] + #[should_panic(expected = "Failed to parse a version requirement.")] + async fn errors_if_no_field_is_valid() { + let sandbox = create_empty_proto_sandbox(); + let plugin = sandbox.create_plugin("npm-test").await; + + plugin + .parse_version_file(ParseVersionFileInput { + content: r#"{ "packageManager": "npm@https://registry.npmjs.org/npm/-/npm-9.0.0.tgz" }"# + .into(), + file: "package.json".into(), + ..Default::default() + }) + .await; + } + mod npm { use super::*; diff --git a/tools/node/CHANGELOG.md b/tools/node/CHANGELOG.md index d5466f9c..d92de84e 100644 --- a/tools/node/CHANGELOG.md +++ b/tools/node/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## Unreleased + +#### 🚀 Updates + +- Updated version detection to loop through all possible fields (`packageManager`, `engines`, etc) to find an applicable version, instead of failing on the first invalid version. + ## 0.17.11 #### 🚀 Updates diff --git a/tools/node/src/proto.rs b/tools/node/src/proto.rs index e0b4f298..1c231f24 100644 --- a/tools/node/src/proto.rs +++ b/tools/node/src/proto.rs @@ -57,20 +57,40 @@ pub fn parse_version_file( if input.file == "package.json" { if let Ok(package_json) = json::from_str::(&input.content) { + let mut candidates = vec![]; + if let Some(constraint) = extract_dev_engine_runtime_version(&package_json, "node") { - version = Some(UnresolvedVersionSpec::parse(constraint)?); + candidates.push(constraint); } - if version.is_none() - && let Some(constraint) = extract_volta_version(&package_json, &input.path, "node")? - { - version = Some(UnresolvedVersionSpec::parse(constraint)?); + if let Some(constraint) = extract_volta_version(&package_json, &input.path, "node")? { + candidates.push(constraint); + } + + if let Some(constraint) = extract_engine_version(&package_json, "node") { + candidates.push(constraint); + } + + let mut error = None; + + for candidates in candidates { + match UnresolvedVersionSpec::parse(candidates) { + Ok(spec) => { + version = Some(spec); + break; + } + Err(err) => { + if error.is_none() { + error = Some(err); + } + } + }; } if version.is_none() - && let Some(constraint) = extract_engine_version(&package_json, "node") + && let Some(error) = error { - version = Some(UnresolvedVersionSpec::parse(constraint)?); + return Err(plugin_err!("{error}")); } } } else if let Some(constraint) = extract_version_from_text(&input.content) { diff --git a/tools/node/tests/versions_test.rs b/tools/node/tests/versions_test.rs index 4b62adfe..967b61ad 100644 --- a/tools/node/tests/versions_test.rs +++ b/tools/node/tests/versions_test.rs @@ -113,6 +113,61 @@ mod node_tool { ); } + #[tokio::test(flavor = "multi_thread")] + async fn parses_first_field_when_multiple_are_valid() { + let sandbox = create_empty_proto_sandbox(); + let plugin = sandbox.create_plugin("node-test").await; + + assert_eq!( + plugin + .parse_version_file(ParseVersionFileInput { + content: r#"{ "devEngines": { "runtime": { "name": "node", "version": "^20" } }, "volta": { "node": "18.1.0" }, "engines": { "node": ">=16" } }"# + .into(), + file: "package.json".into(), + ..Default::default() + }) + .await, + ParseVersionFileOutput { + version: Some(UnresolvedVersionSpec::parse("^20").unwrap()), + } + ); + } + + #[tokio::test(flavor = "multi_thread")] + async fn skips_invalid_field_and_parses_next_valid_field() { + let sandbox = create_empty_proto_sandbox(); + let plugin = sandbox.create_plugin("node-test").await; + + assert_eq!( + plugin + .parse_version_file(ParseVersionFileInput { + content: r#"{ "volta": { "node": "invalid version" }, "engines": { "node": ">=18" } }"# + .into(), + file: "package.json".into(), + ..Default::default() + }) + .await, + ParseVersionFileOutput { + version: Some(UnresolvedVersionSpec::parse(">=18").unwrap()), + } + ); + } + + #[tokio::test(flavor = "multi_thread")] + #[should_panic(expected = "Failed to parse a version requirement.")] + async fn errors_if_no_field_is_valid() { + let sandbox = create_empty_proto_sandbox(); + let plugin = sandbox.create_plugin("node-test").await; + + plugin + .parse_version_file(ParseVersionFileInput { + content: r#"{ "volta": { "node": "invalid version" } }"#.into(), + file: "package.json".into(), + ..Default::default() + }) + .await; + } + #[tokio::test(flavor = "multi_thread")] async fn parses_nvmrc() { let sandbox = create_empty_proto_sandbox(); From a7509d9f27cf72a6e074a11ab9e60ac9bb6f9afd Mon Sep 17 00:00:00 2001 From: Miles Johnson Date: Sat, 8 Aug 2026 18:33:43 -0700 Subject: [PATCH 36/78] new: Add Nub support. (#178) --- backends/cargo/CHANGELOG.md | 6 + backends/cargo/README.md | 2 + backends/cargo/src/config.rs | 6 + backends/cargo/src/proto.rs | 4 + backends/cargo/tests/download_test.rs | 23 ++ toolchains/node-depman/src/tier1.rs | 2 + tools/node-depman/CHANGELOG.md | 1 + tools/node-depman/src/package_manager.rs | 127 +++++-- tools/node-depman/src/proto.rs | 149 +++++---- tools/node-depman/tests/activate_test.rs | 79 ++++- tools/node-depman/tests/download_test.rs | 312 +++++++++++++++++- tools/node-depman/tests/metadata_test.rs | 25 ++ tools/node-depman/tests/shims_test.rs | 6 + ..._node_depman_tool__nub__creates_shims.snap | 14 + tools/node-depman/tests/versions_test.rs | 122 +++++++ 15 files changed, 774 insertions(+), 104 deletions(-) create mode 100644 tools/node-depman/tests/snapshots/shims_test__node_depman_tool__nub__creates_shims.snap diff --git a/backends/cargo/CHANGELOG.md b/backends/cargo/CHANGELOG.md index 5e113733..215e01f9 100644 --- a/backends/cargo/CHANGELOG.md +++ b/backends/cargo/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## Unreleased + +#### 🚀 Updates + +- Added a `locked` config setting, which will be passed to install commands as `--locked`. + ## 0.1.5 #### 🚀 Updates diff --git a/backends/cargo/README.md b/backends/cargo/README.md index d9bf7e18..d73120fe 100644 --- a/backends/cargo/README.md +++ b/backends/cargo/README.md @@ -17,6 +17,7 @@ Cargo plugin can be configured with a `.prototools` file. - `bin` (string) - The name of an explicit binary within the package to install. - `features` (string[]) - List of Cargo features to enable for the package. +- `locked` (boolean) - Use locked versions and don't update `Cargo.lock`. - `no-default-features` (bool) - Disable the `default` feature of the package. - `registry` (string) - A custom registry to install the package from. @@ -29,6 +30,7 @@ features = ["std"] ### For backend +- `locked` (boolean) - Use locked versions and don't update `Cargo.lock`. - `no-binstall` (bool) - Do not use [cargo-binstall](https://crates.io/crates/cargo-binstall) for installing packages, and instead build from source. - `registry` (string) - A custom registry to install packages from. diff --git a/backends/cargo/src/config.rs b/backends/cargo/src/config.rs index ad6ba222..24f46fd1 100644 --- a/backends/cargo/src/config.rs +++ b/backends/cargo/src/config.rs @@ -5,6 +5,9 @@ use serde::{Deserialize, Serialize}; #[derive(Debug, Default, Deserialize, Serialize, Schematic)] #[serde(default, deny_unknown_fields, rename_all = "kebab-case")] pub struct CargoBackendConfig { + /// Install using locked versions. + pub locked: bool, + /// Do not use `cargo-binstall` even when available. pub no_binstall: bool, @@ -22,6 +25,9 @@ pub struct CargoToolConfig { /// List of features to enable for the package. pub features: Vec, + /// Install using locked versions. + pub locked: bool, + /// Custom Git URL to the package. // pub git_url: Option, diff --git a/backends/cargo/src/proto.rs b/backends/cargo/src/proto.rs index 1ed316f0..ee01dc62 100644 --- a/backends/cargo/src/proto.rs +++ b/backends/cargo/src/proto.rs @@ -93,6 +93,10 @@ pub fn native_install( // What to install command.args.push(format!("{id}@{}", input.context.version)); + if tool_config.locked || backend_config.locked { + command.args.push("--locked".into()); + } + // if let Some(git) = &tool_config.git_url { // command.args.push("--git".into()); // command.args.push(git.into()); diff --git a/backends/cargo/tests/download_test.rs b/backends/cargo/tests/download_test.rs index 9e5aa53b..8e0eb2fc 100644 --- a/backends/cargo/tests/download_test.rs +++ b/backends/cargo/tests/download_test.rs @@ -21,6 +21,29 @@ mod cargo_backend_download { }); } + mod locked { + use super::*; + + generate_native_install_tests!("cargo:eza", "0.23.1", None, |cfg| { + cfg.tool_config(CargoToolConfig { + locked: true, + ..Default::default() + }); + }); + } + + mod locked_without_binstall { + use super::*; + + generate_native_install_tests!("cargo:eza", "0.23.1", None, |cfg| { + cfg.backend_config(CargoBackendConfig { + locked: true, + no_binstall: true, + ..Default::default() + }); + }); + } + // https://github.com/kbknapp/cargo-outdated/blob/master/Cargo.toml mod features { diff --git a/toolchains/node-depman/src/tier1.rs b/toolchains/node-depman/src/tier1.rs index 1f023b62..3d091733 100644 --- a/toolchains/node-depman/src/tier1.rs +++ b/toolchains/node-depman/src/tier1.rs @@ -21,6 +21,7 @@ pub fn register_toolchain( lock_file_names: vec!["package-lock.json".into(), "npm-shrinkwrap.json".into()], ..Default::default() }, + PackageManager::Nub => todo!(), PackageManager::Pnpm | PackageManager::Pnpm11 | PackageManager::Pnpm12 => { RegisterToolchainOutput { config_file_globs: vec![ @@ -72,6 +73,7 @@ pub fn define_toolchain_config() -> FnResult> Ok(Json(DefineToolchainConfigOutput { schema: match manager { PackageManager::Npm => SchemaBuilder::build_root::(), + PackageManager::Nub => todo!(), PackageManager::Pnpm | PackageManager::Pnpm11 | PackageManager::Pnpm12 => { SchemaBuilder::build_root::() } diff --git a/tools/node-depman/CHANGELOG.md b/tools/node-depman/CHANGELOG.md index 6e9a4522..724f31b7 100644 --- a/tools/node-depman/CHANGELOG.md +++ b/tools/node-depman/CHANGELOG.md @@ -4,6 +4,7 @@ #### 🚀 Updates +- Added experimental support for Nub: https://nubjs.com/. - Added experimental support for pnpm v12 (Rust based). - Updated version detection to loop through all possible fields (`packageManager`, `engines`, etc) to find an applicable version, instead of failing on the first invalid version. diff --git a/tools/node-depman/src/package_manager.rs b/tools/node-depman/src/package_manager.rs index 7840d92a..f79530ff 100644 --- a/tools/node-depman/src/package_manager.rs +++ b/tools/node-depman/src/package_manager.rs @@ -16,8 +16,10 @@ use starbase_utils::{fs::find_upwards, yaml}; pub enum PackageManager { Npm, + Nub, + Pnpm, - // Major changes + // Major changes / New shims Pnpm11, // Rust based Pnpm12, @@ -37,6 +39,8 @@ impl PackageManager { Self::Yarn1 } else if id.to_lowercase().contains("pnpm") { Self::Pnpm + } else if id.to_lowercase().contains("nub") { + Self::Nub } else { Self::Npm }) @@ -98,6 +102,10 @@ impl PackageManager { matches!(self, Self::Npm) } + pub fn is_nub(&self) -> bool { + matches!(self, Self::Nub) + } + pub fn is_pnpm(&self) -> bool { matches!(self, Self::Pnpm | Self::Pnpm11 | Self::Pnpm12) } @@ -106,9 +114,14 @@ impl PackageManager { matches!(self, Self::Yarn1 | Self::Yarn2to5 | Self::Yarn6) } + pub fn is_rust_based(&self) -> bool { + matches!(self, Self::Nub | Self::Pnpm12 | Self::Yarn6) + } + pub fn get_bin_name(&self) -> String { match self { Self::Npm => "npm".into(), + Self::Nub => "nub".into(), Self::Pnpm | Self::Pnpm11 | Self::Pnpm12 => "pnpm".into(), Self::Yarn1 | Self::Yarn2to5 | Self::Yarn6 => "yarn".into(), } @@ -116,39 +129,49 @@ impl PackageManager { pub fn get_package_name(&self) -> String { match self { + Self::Nub => "@nubjs/nub".into(), Self::Yarn2to5 => "@yarnpkg/cli-dist".into(), _ => self.get_bin_name(), } } pub fn get_package_name_for_download(&self, env: &HostEnvironment) -> AnyResult { + let arch = match env.arch { + HostArch::Arm64 => "arm64", + HostArch::X64 => "x64", + other => { + return Err(PluginError::UnsupportedArch { + tool: self.get_bin_name(), + arch: other.to_string(), + } + .into()); + } + }; + + let os = match env.os { + HostOS::MacOS => "darwin", + HostOS::Linux => "linux", + HostOS::Windows => "win32", + other => { + return Err(PluginError::UnsupportedOS { + tool: self.get_bin_name(), + os: other.to_string(), + } + .into()); + } + }; + match self { - Self::Pnpm12 => { - let arch = match env.arch { - HostArch::Arm64 => "arm64", - HostArch::X64 => "x64", - other => { - return Err(PluginError::UnsupportedArch { - tool: "pnpm".into(), - arch: other.to_string(), - } - .into()); - } - }; - - let os = match env.os { - HostOS::MacOS => "darwin", - HostOS::Linux => "linux", - HostOS::Windows => "windows", - other => { - return Err(PluginError::UnsupportedOS { - tool: "pnpm".into(), - os: other.to_string(), - } - .into()); - } - }; + Self::Nub => { + let mut name = format!("@nubjs/nub-{os}-{arch}"); + + if env.libc == HostLibc::Musl { + name.push_str("-musl"); + } + Ok(name) + } + Self::Pnpm12 => { let mut name = format!("@pnpm/exe.{os}-{arch}"); if env.libc == HostLibc::Musl { @@ -161,6 +184,56 @@ impl PackageManager { } } + pub fn get_global_lookup_dirs(&self, env: &HostEnvironment) -> Vec { + let mut dirs: Vec = vec![ + "$PREFIX/bin".into(), + "$PREFIX/shims".into(), + "$PROTO_HOME/tools/node/$PROTO_NODE_VERSION/bin".into(), + ]; + + match self { + // https://docs.npmjs.com/cli/v9/configuring-npm/folders#prefix-configuration + // https://github.com/npm/cli/blob/latest/lib/npm.js + // https://github.com/npm/cli/blob/latest/workspaces/config/lib/index.js#L339 + Self::Npm => { + dirs.push("$TOOL_DIR/shims".into()); + } + // Nub's global layout is modeled on pnpm v11 and intentionally + // shares pnpm's directories. There is no NUB_HOME. + // https://github.com/nubjs/nub/blob/main/vendor/aube/crates/aube/src/commands/global.rs + Self::Nub | + // https://pnpm.io/npmrc#global-dir + // https://github.com/pnpm/pnpm/blob/main/config/config/src/index.ts#L350 + // https://github.com/pnpm/pnpm/blob/main/config/config/src/dirs.ts#L40 + Self::Pnpm | Self::Pnpm11 | Self::Pnpm12 => { + dirs.push("$PNPM_HOME".into()); + + if env.os.is_windows() { + dirs.push("$LOCALAPPDATA/pnpm".into()); + } else if env.os.is_mac() { + dirs.push("$HOME/Library/pnpm".into()); + } else { + dirs.push("$XDG_DATA_HOME/pnpm".into()); + dirs.push("$HOME/.local/share/pnpm".into()); + } + } + // https://github.com/yarnpkg/yarn/blob/master/src/cli/commands/global.js#L84 + Self::Yarn1 | Self::Yarn2to5 | Self::Yarn6 => { + if env.os.is_windows() { + dirs.push("$LOCALAPPDATA/Yarn/bin".into()); + } + + dirs.push("$HOME/.yarn/bin".into()); + } + } + + if env.os.is_windows() { + return dirs.into_iter().map(|dir| dir.replace("/", "\\")).collect(); + } + + dirs + } + pub fn get_http_headers( &self, registry_url: &str, @@ -170,7 +243,7 @@ impl PackageManager { let url = parse_registry_url(registry_url)?; let credentials = match self { - Self::Npm | Self::Pnpm | Self::Pnpm11 | Self::Pnpm12 => { + Self::Npm | Self::Nub | Self::Pnpm | Self::Pnpm11 | Self::Pnpm12 => { let rc = NpmrcConfig::load_with_options(LoadOptions { cwd: Some(working_dir.into()), global_prefix: None, diff --git a/tools/node-depman/src/proto.rs b/tools/node-depman/src/proto.rs index 6f6c95a8..8ce78c85 100644 --- a/tools/node-depman/src/proto.rs +++ b/tools/node-depman/src/proto.rs @@ -33,16 +33,20 @@ pub fn register_tool(Json(_): Json) -> FnResult { + if input.initial == UnresolvedVersionSpec::Canary { + output.candidate = Some(UnresolvedVersionSpec::parse("^12.0.0-rc.0")?); + } else if let UnresolvedVersionSpec::Alias(alias) = input.initial + && (alias == "rust" || alias == "next") + { + output.candidate = Some(UnresolvedVersionSpec::parse("^12.0.0-rc.0")?); + } + } + PackageManager::Yarn1 | PackageManager::Yarn2to5 | PackageManager::Yarn6 => { if input.initial == UnresolvedVersionSpec::Canary { output.candidate = Some(UnresolvedVersionSpec::parse("^6.0.0-rc.0")?); @@ -441,100 +455,67 @@ pub fn locate_executables( Json(input): Json, ) -> FnResult> { let env = get_host_environment()?; + let config = get_tool_config::()?; let manager = PackageManager::detect_from_version(&input.context.version)?; + let rust_based = manager.is_rust_based(); let mut secondary = FxHashMap::::default(); let primary; - if !input.install_dir.join("shims").exists() { + if !rust_based && !input.install_dir.join("shims").exists() { create_internal_shims(env, &input.install_dir, &manager)?; } - // These are the directories that contain the executable binaries, - // NOT where the packages/node modules are stored. Some package managers - // have separate folders for the 2 processes, and then create symlinks. - let mut globals_lookup_dirs = vec![ - "$PREFIX/bin".into(), - "$PREFIX/shims".into(), - "$PROTO_HOME/tools/node/$PROTO_NODE_VERSION/bin".into(), - ]; - match &manager { PackageManager::Npm => { primary = ExecutableConfig::new_primary("shims/npm"); - - // npx secondary.insert("npx".into(), ExecutableConfig::new("shims/npx")); - - // node-gyp secondary.insert( "node-gyp".into(), ExecutableConfig::with_parent("node_modules/node-gyp/bin/node-gyp.js", "node"), ); - - // https://docs.npmjs.com/cli/v9/configuring-npm/folders#prefix-configuration - // https://github.com/npm/cli/blob/latest/lib/npm.js - // https://github.com/npm/cli/blob/latest/workspaces/config/lib/index.js#L339 - globals_lookup_dirs.push("$TOOL_DIR/shims".into()); } - PackageManager::Pnpm | PackageManager::Pnpm11 | PackageManager::Pnpm12 => { - if manager == PackageManager::Pnpm12 { - let exe_name = env.os.get_exe_name("pnpm"); - - primary = ExecutableConfig::new_primary(&exe_name); - secondary.insert("pn".into(), ExecutableConfig::new(&exe_name)); - - let pnpx_config = ExecutableConfig::new(exe_name) - .no_bin(true) - .shim_before_args(StringOrVec::String("dlx".into())); - - secondary.insert("pnpx".into(), pnpx_config.clone()); - secondary.insert("pnx".into(), pnpx_config); - } else { - primary = ExecutableConfig::new_primary("shims/pnpm"); + PackageManager::Nub => { + let exe_name = env.os.get_exe_name("bin/nub"); - // pnpx - secondary.insert("pnpx".into(), ExecutableConfig::new("shims/pnpx")); + primary = ExecutableConfig::new_primary(&exe_name); + secondary.insert("nubx".into(), ExecutableConfig::new(exe_name)); + } + PackageManager::Pnpm => { + primary = ExecutableConfig::new_primary("shims/pnpm"); + secondary.insert("pnpx".into(), ExecutableConfig::new("shims/pnpx")); + } + PackageManager::Pnpm11 => { + primary = ExecutableConfig::new_primary("shims/pnpm"); + secondary.insert("pnpx".into(), ExecutableConfig::new("shims/pnpx")); + secondary.insert("pn".into(), ExecutableConfig::new("shims/pn")); + secondary.insert("pnx".into(), ExecutableConfig::new("shims/pnx")); + } + PackageManager::Pnpm12 => { + let exe_name = env.os.get_exe_name("pnpm"); - if manager == PackageManager::Pnpm11 { - secondary.insert("pn".into(), ExecutableConfig::new("shims/pn")); - secondary.insert("pnx".into(), ExecutableConfig::new("shims/pnx")); - } - } + primary = ExecutableConfig::new_primary(&exe_name); + secondary.insert("pn".into(), ExecutableConfig::new(&exe_name)); - // https://pnpm.io/npmrc#global-dir - // https://github.com/pnpm/pnpm/blob/main/config/config/src/index.ts#L350 - // https://github.com/pnpm/pnpm/blob/main/config/config/src/dirs.ts#L40 - globals_lookup_dirs.push("$PNPM_HOME".into()); + let pnpx_config = ExecutableConfig::new(exe_name) + .no_bin(true) + .shim_before_args(StringOrVec::String("dlx".into())); - if env.os.is_windows() { - globals_lookup_dirs.push("$LOCALAPPDATA\\pnpm".into()); - } else if env.os.is_mac() { - globals_lookup_dirs.push("$HOME/Library/pnpm".into()); - } else { - globals_lookup_dirs.push("$HOME/.local/share/pnpm".into()); - } + secondary.insert("pnpx".into(), pnpx_config.clone()); + secondary.insert("pnx".into(), pnpx_config); } - PackageManager::Yarn1 | PackageManager::Yarn2to5 | PackageManager::Yarn6 => { - if manager == PackageManager::Yarn6 { - primary = ExecutableConfig::new_primary(env.os.get_exe_name("yarn-bin")); - } else { - primary = ExecutableConfig::new_primary("shims/yarn"); - - // yarnpkg - secondary.insert("yarnpkg".into(), ExecutableConfig::new("shims/yarn")); - } - - // https://github.com/yarnpkg/yarn/blob/master/src/cli/commands/global.js#L84 - if env.os.is_windows() { - globals_lookup_dirs.push("$LOCALAPPDATA\\Yarn\\bin".into()); - globals_lookup_dirs.push("$HOME\\.yarn\\bin".into()); - } else { - globals_lookup_dirs.push("$HOME/.yarn/bin".into()); - } + PackageManager::Yarn1 | PackageManager::Yarn2to5 => { + primary = ExecutableConfig::new_primary("shims/yarn"); + secondary.insert("yarnpkg".into(), ExecutableConfig::new("shims/yarn")); + } + PackageManager::Yarn6 => { + primary = ExecutableConfig::new_primary(env.os.get_exe_name("yarn-bin")); } }; - let config = get_tool_config::()?; + // These are the directories that contain the executable binaries, + // NOT where the packages/node modules are stored. Some package managers + // have separate folders for the 2 processes, and then create symlinks. + let mut globals_lookup_dirs = manager.get_global_lookup_dirs(env); // If only shared dir, clear everything else if config.shared_globals_dir { @@ -549,6 +530,11 @@ pub fn locate_executables( // Update the permissions of each executable since they are custom shims exes.iter_mut().for_each(|(name, config)| { + if rust_based { + return; + } + + // Only applies to shims, not real binaries! config.no_bin = true; if name != "node-gyp" { @@ -605,6 +591,19 @@ pub fn activate_environment( ); } + // Nub mirrors pnpm's global-dir/global-bin-dir split, but only + // the npm-compat env family is always honored (pnpm_config_* + // requires pnpm to be the incumbent package manager, and PREFIX + // is never read for install locations). + PackageManager::Nub => { + output + .env + .insert("npm_config_global_dir".into(), globals_root_dir); + output + .env + .insert("npm_config_global_bin_dir".into(), globals_bin_dir); + } + // Pnpm has explicit support for the bin and root dirs, // which makes this super simple to handle. PackageManager::Pnpm | PackageManager::Pnpm11 | PackageManager::Pnpm12 => { @@ -670,8 +669,8 @@ fn create_internal_shims( PackageManager::Yarn1 | PackageManager::Yarn2to5 => { create_internal_shim(env, tool_dir, "yarn", "yarn.js")?; } - // Yarn v6+ and pnpm v12+ is a native binary and requires no shims - PackageManager::Yarn6 | PackageManager::Pnpm12 => {} + // nub, yarn v6+, and pnpm v12+ are a native binary and require no shims + PackageManager::Nub | PackageManager::Pnpm12 | PackageManager::Yarn6 => {} }; Ok(()) diff --git a/tools/node-depman/tests/activate_test.rs b/tools/node-depman/tests/activate_test.rs index fff430ce..053dc1ed 100644 --- a/tools/node-depman/tests/activate_test.rs +++ b/tools/node-depman/tests/activate_test.rs @@ -90,6 +90,84 @@ mod node_depman_tool { } } + mod nub { + use super::*; + + #[tokio::test(flavor = "multi_thread")] + + async fn does_nothing_if_not_configured() { + let sandbox = create_empty_proto_sandbox(); + let plugin = sandbox.create_plugin("nub-test").await; + + let result = plugin + .activate_environment(ActivateEnvironmentInput::default()) + .await; + + assert!(result.env.is_empty()); + } + + #[tokio::test(flavor = "multi_thread")] + + async fn does_nothing_if_disabled() { + let sandbox = create_empty_proto_sandbox(); + let plugin = sandbox + .create_plugin_with_config("nub-test", |config| { + config.tool_config(NodeDepmanPluginConfig { + shared_globals_dir: false, + }); + }) + .await; + + let result = plugin + .activate_environment(ActivateEnvironmentInput::default()) + .await; + + assert!(result.env.is_empty()); + } + + #[tokio::test(flavor = "multi_thread")] + + async fn adds_env_var() { + let sandbox = create_empty_proto_sandbox(); + let plugin = sandbox + .create_plugin_with_config("nub-test", |config| { + config.tool_config(NodeDepmanPluginConfig { + shared_globals_dir: true, + }); + }) + .await; + + let result = plugin + .activate_environment(ActivateEnvironmentInput { + globals_dir: Some(create_globals_dir()), + ..Default::default() + }) + .await; + + assert_eq!( + result.env, + HashMap::from_iter([ + ( + "npm_config_global_dir".into(), + sandbox + .path() + .join(".proto/tools/node/globals") + .to_string_lossy() + .to_string() + ), + ( + "npm_config_global_bin_dir".into(), + sandbox + .path() + .join(".proto/tools/node/globals/bin") + .to_string_lossy() + .to_string() + ) + ]) + ); + } + } + mod pnpm { use super::*; @@ -186,7 +264,6 @@ mod node_depman_tool { ..Default::default() }, globals_dir: Some(create_globals_dir()), - ..Default::default() }) .await; diff --git a/tools/node-depman/tests/download_test.rs b/tools/node-depman/tests/download_test.rs index 238d7bf3..5b36c76c 100644 --- a/tools/node-depman/tests/download_test.rs +++ b/tools/node-depman/tests/download_test.rs @@ -173,6 +173,316 @@ mod node_depman_tool { } } + mod nub { + use super::*; + + // Nub is Rust based and downloaded from the npm registry + // as an os/arch specific package: @nubjs/nub-{os}-{arch} + + generate_download_install_tests!("nub-test", "0.7.4"); + + #[tokio::test(flavor = "multi_thread")] + async fn supports_prebuilt_macos_arm64() { + let sandbox = create_empty_proto_sandbox(); + let plugin = sandbox + .create_plugin_with_config("nub-test", |config| { + config.host(HostOS::MacOS, HostArch::Arm64); + }) + .await; + + assert_eq!( + plugin + .download_prebuilt(DownloadPrebuiltInput { + context: PluginContext { + version: VersionSpec::parse("0.7.4").unwrap(), + ..Default::default() + }, + ..Default::default() + }) + .await, + DownloadPrebuiltOutput { + archive_prefix: Some("package".into()), + download_url: "https://registry.npmjs.org/@nubjs/nub-darwin-arm64/-/nub-darwin-arm64-0.7.4.tgz".into(), + ..Default::default() + } + ); + } + + #[tokio::test(flavor = "multi_thread")] + async fn supports_prebuilt_linux_x64_gnu() { + let sandbox = create_empty_proto_sandbox(); + let plugin = sandbox + .create_plugin_with_config("nub-test", |config| { + config.host_with(|host| { + host.os = HostOS::Linux; + host.arch = HostArch::X64; + host.libc = HostLibc::Gnu; + }); + }) + .await; + + // Gnu is supported and has no libc suffix + assert_eq!( + plugin + .download_prebuilt(DownloadPrebuiltInput { + context: PluginContext { + version: VersionSpec::parse("0.7.4").unwrap(), + ..Default::default() + }, + ..Default::default() + }) + .await, + DownloadPrebuiltOutput { + archive_prefix: Some("package".into()), + download_url: "https://registry.npmjs.org/@nubjs/nub-linux-x64/-/nub-linux-x64-0.7.4.tgz".into(), + ..Default::default() + } + ); + } + + #[tokio::test(flavor = "multi_thread")] + async fn supports_prebuilt_linux_arm64_musl() { + let sandbox = create_empty_proto_sandbox(); + let plugin = sandbox + .create_plugin_with_config("nub-test", |config| { + config.host_with(|host| { + host.os = HostOS::Linux; + host.arch = HostArch::Arm64; + host.libc = HostLibc::Musl; + }); + }) + .await; + + assert_eq!( + plugin + .download_prebuilt(DownloadPrebuiltInput { + context: PluginContext { + version: VersionSpec::parse("0.7.4").unwrap(), + ..Default::default() + }, + ..Default::default() + }) + .await, + DownloadPrebuiltOutput { + archive_prefix: Some("package".into()), + download_url: "https://registry.npmjs.org/@nubjs/nub-linux-arm64-musl/-/nub-linux-arm64-musl-0.7.4.tgz".into(), + ..Default::default() + } + ); + } + + #[tokio::test(flavor = "multi_thread")] + async fn supports_prebuilt_windows_x64() { + let sandbox = create_empty_proto_sandbox(); + let plugin = sandbox + .create_plugin_with_config("nub-test", |config| { + config.host(HostOS::Windows, HostArch::X64); + }) + .await; + + assert_eq!( + plugin + .download_prebuilt(DownloadPrebuiltInput { + context: PluginContext { + version: VersionSpec::parse("0.7.4").unwrap(), + ..Default::default() + }, + ..Default::default() + }) + .await, + DownloadPrebuiltOutput { + archive_prefix: Some("package".into()), + download_url: "https://registry.npmjs.org/@nubjs/nub-win32-x64/-/nub-win32-x64-0.7.4.tgz".into(), + ..Default::default() + } + ); + } + + #[tokio::test(flavor = "multi_thread")] + async fn downloads_from_custom_registry() { + let sandbox = create_empty_proto_sandbox(); + let plugin = sandbox + .create_plugin_with_config("nub-test", |config| { + config + .host(HostOS::MacOS, HostArch::X64) + .tool_config(NodeDepmanPluginConfig { + registry_url: "https://some-internal-url.example".into(), + }); + }) + .await; + + assert_eq!( + plugin + .download_prebuilt(DownloadPrebuiltInput { + context: PluginContext { + version: VersionSpec::parse("0.7.4").unwrap(), + ..Default::default() + }, + ..Default::default() + }) + .await, + DownloadPrebuiltOutput { + archive_prefix: Some("package".into()), + download_url: "https://some-internal-url.example/@nubjs/nub-darwin-x64/-/nub-darwin-x64-0.7.4.tgz".into(), + ..Default::default() + } + ); + } + + #[tokio::test(flavor = "multi_thread")] + async fn extracts_auth_token_header() { + let sandbox = create_empty_proto_sandbox(); + sandbox.create_file(".npmrc", "//registry.npmjs.org/:_authToken = abc123"); + + let plugin = sandbox + .create_plugin_with_config("nub-test", |config| { + config.host(HostOS::Linux, HostArch::Arm64); + }) + .await; + + assert_eq!( + plugin + .download_prebuilt(DownloadPrebuiltInput { + context: PluginContext { + version: VersionSpec::parse("0.7.4").unwrap(), + working_dir: plugin.tool.to_virtual_path(sandbox.path()), + ..Default::default() + }, + ..Default::default() + }) + .await, + DownloadPrebuiltOutput { + archive_prefix: Some("package".into()), + download_url: "https://registry.npmjs.org/@nubjs/nub-linux-arm64/-/nub-linux-arm64-0.7.4.tgz".into(), + http_headers: FxHashMap::from_iter([( + "Authorization".into(), + "Bearer abc123".into() + )]), + ..Default::default() + } + ); + } + + #[tokio::test(flavor = "multi_thread")] + #[should_panic(expected = "unsupported architecture x86.")] + async fn doesnt_support_other_archs() { + let sandbox = create_empty_proto_sandbox(); + let plugin = sandbox + .create_plugin_with_config("nub-test", |config| { + config.host(HostOS::Linux, HostArch::X86); + }) + .await; + + plugin + .download_prebuilt(DownloadPrebuiltInput { + context: PluginContext { + version: VersionSpec::parse("0.7.4").unwrap(), + ..Default::default() + }, + ..Default::default() + }) + .await; + } + + #[tokio::test(flavor = "multi_thread")] + #[should_panic(expected = "unsupported OS freebsd.")] + async fn doesnt_support_other_os() { + let sandbox = create_empty_proto_sandbox(); + let plugin = sandbox + .create_plugin_with_config("nub-test", |config| { + config.host(HostOS::FreeBSD, HostArch::Arm64); + }) + .await; + + plugin + .download_prebuilt(DownloadPrebuiltInput { + context: PluginContext { + version: VersionSpec::parse("0.7.4").unwrap(), + ..Default::default() + }, + ..Default::default() + }) + .await; + } + + #[tokio::test(flavor = "multi_thread")] + #[should_panic(expected = "nub does not support canary/nightly versions.")] + async fn doesnt_support_canary() { + let sandbox = create_empty_proto_sandbox(); + let plugin = sandbox + .create_plugin_with_config("nub-test", |config| { + config.host(HostOS::MacOS, HostArch::Arm64); + }) + .await; + + plugin + .download_prebuilt(DownloadPrebuiltInput { + context: PluginContext { + version: VersionSpec::parse("canary").unwrap(), + ..Default::default() + }, + ..Default::default() + }) + .await; + } + + #[tokio::test(flavor = "multi_thread")] + async fn locates_native_bin() { + let sandbox = create_empty_proto_sandbox(); + let plugin = sandbox + .create_plugin_with_config("nub-test", |config| { + config.host(HostOS::MacOS, HostArch::Arm64); + }) + .await; + + let exes = plugin + .locate_executables(LocateExecutablesInput { + context: PluginContext { + version: VersionSpec::parse("0.7.4").unwrap(), + ..Default::default() + }, + install_dir: plugin.tool.to_virtual_path(sandbox.path()), + }) + .await + .exes; + + assert_eq!(exes.get("nub").unwrap().exe_path, Some("bin/nub".into())); + + // nubx runs through the primary binary + assert_eq!(exes.get("nubx").unwrap().exe_path, Some("bin/nub".into())); + + // No internal shims are created for the native binary + assert!(!sandbox.path().join("shims/nub").exists()); + } + + #[tokio::test(flavor = "multi_thread")] + async fn locates_native_bin_windows() { + let sandbox = create_empty_proto_sandbox(); + let plugin = sandbox + .create_plugin_with_config("nub-test", |config| { + config.host(HostOS::Windows, HostArch::X64); + }) + .await; + + let exes = plugin + .locate_executables(LocateExecutablesInput { + context: PluginContext { + version: VersionSpec::parse("0.7.4").unwrap(), + ..Default::default() + }, + install_dir: plugin.tool.to_virtual_path(sandbox.path()), + }) + .await + .exes; + + // The .exe extension must not be rewritten to .cmd + assert_eq!( + exes.get("nub").unwrap().exe_path, + Some("bin/nub.exe".into()) + ); + } + } + mod pnpm { use super::*; @@ -482,7 +792,7 @@ mod node_depman_tool { .await, DownloadPrebuiltOutput { archive_prefix: Some("package".into()), - download_url: "https://registry.npmjs.org/@pnpm/exe.windows-x64/-/exe.windows-x64-12.0.0.tgz".into(), + download_url: "https://registry.npmjs.org/@pnpm/exe.win32-x64/-/exe.win32-x64-12.0.0.tgz".into(), ..Default::default() } ); diff --git a/tools/node-depman/tests/metadata_test.rs b/tools/node-depman/tests/metadata_test.rs index 4f265f48..733d640e 100644 --- a/tools/node-depman/tests/metadata_test.rs +++ b/tools/node-depman/tests/metadata_test.rs @@ -27,6 +27,31 @@ mod node_depman_tool { } } + mod nub { + use super::*; + + #[tokio::test(flavor = "multi_thread")] + async fn registers_metadata() { + let sandbox = create_empty_proto_sandbox(); + let plugin = sandbox.create_plugin("nub-test").await; + + let metadata = plugin.register_tool(create_metadata("nub-test")).await; + + assert_eq!(metadata.name, "nub"); + + // Binaries are os/arch specific, so records must be scoped + assert!(!metadata.lock_options.ignore_os_arch); + assert_eq!(metadata.type_of, PluginType::DependencyManager); + assert_eq!( + metadata.plugin_version.unwrap().to_string(), + env!("CARGO_PKG_VERSION") + ); + + // Unlike the other package managers, nub does not require Node.js + assert!(metadata.requires.is_empty()); + } + } + mod pnpm { use super::*; diff --git a/tools/node-depman/tests/shims_test.rs b/tools/node-depman/tests/shims_test.rs index b32e208d..dd5c7a14 100644 --- a/tools/node-depman/tests/shims_test.rs +++ b/tools/node-depman/tests/shims_test.rs @@ -8,6 +8,12 @@ mod node_depman_tool { generate_shims_test!("npm-test", ["npm", "npx", "node-gyp"]); } + mod nub { + use super::*; + + generate_shims_test!("nub-test", ["nub", "nubx"]); + } + mod pnpm { use super::*; diff --git a/tools/node-depman/tests/snapshots/shims_test__node_depman_tool__nub__creates_shims.snap b/tools/node-depman/tests/snapshots/shims_test__node_depman_tool__nub__creates_shims.snap new file mode 100644 index 00000000..21d263dc --- /dev/null +++ b/tools/node-depman/tests/snapshots/shims_test__node_depman_tool__nub__creates_shims.snap @@ -0,0 +1,14 @@ +--- +source: tools/node-depman/tests/shims_test.rs +expression: "std :: fs ::\nread_to_string(sandbox.path().join(\".proto/shims/registry.json\")).unwrap()" +--- +{ + "nub": { + "alt_exe": true, + "context": "nub-test" + }, + "nubx": { + "alt_exe": true, + "context": "nub-test" + } +} diff --git a/tools/node-depman/tests/versions_test.rs b/tools/node-depman/tests/versions_test.rs index 4b819de4..265ba6cd 100644 --- a/tools/node-depman/tests/versions_test.rs +++ b/tools/node-depman/tests/versions_test.rs @@ -226,6 +226,128 @@ mod node_depman_tool { } } + mod nub { + use super::*; + + generate_resolve_versions_tests!("nub-test", { + "0.2" => "0.2.10", + "0.4" => "0.4.13", + "0.5.0" => "0.5.0", + }); + + #[tokio::test(flavor = "multi_thread")] + async fn doesnt_parse_package_manager_if_diff_name() { + let sandbox = create_empty_proto_sandbox(); + let plugin = sandbox.create_plugin("nub-test").await; + + assert_eq!( + plugin + .parse_version_file(ParseVersionFileInput { + content: r#"{ "packageManager": "npm@1.2.3" }"#.into(), + file: "package.json".into(), + ..Default::default() + }) + .await, + ParseVersionFileOutput { version: None } + ); + } + + #[tokio::test(flavor = "multi_thread")] + async fn parses_package_manager() { + let sandbox = create_empty_proto_sandbox(); + let plugin = sandbox.create_plugin("nub-test").await; + + assert_eq!( + plugin + .parse_version_file(ParseVersionFileInput { + content: r#"{ "packageManager": "nub@1.2.3" }"#.into(), + file: "package.json".into(), + ..Default::default() + }) + .await, + ParseVersionFileOutput { + version: Some(UnresolvedVersionSpec::parse("1.2.3").unwrap()), + } + ); + } + + #[tokio::test(flavor = "multi_thread")] + async fn parses_package_manager_latest() { + let sandbox = create_empty_proto_sandbox(); + let plugin = sandbox.create_plugin("nub-test").await; + + assert_eq!( + plugin + .parse_version_file(ParseVersionFileInput { + content: r#"{ "packageManager": "nub" }"#.into(), + file: "package.json".into(), + ..Default::default() + }) + .await, + ParseVersionFileOutput { + version: Some(UnresolvedVersionSpec::parse("latest").unwrap()), + } + ); + } + + #[tokio::test(flavor = "multi_thread")] + async fn parses_engines() { + let sandbox = create_empty_proto_sandbox(); + let plugin = sandbox.create_plugin("nub-test").await; + + assert_eq!( + plugin + .parse_version_file(ParseVersionFileInput { + content: r#"{ "engines": { "nub": "1.2.3" } }"#.into(), + file: "package.json".into(), + ..Default::default() + }) + .await, + ParseVersionFileOutput { + version: Some(UnresolvedVersionSpec::parse("1.2.3").unwrap()), + } + ); + } + + #[tokio::test(flavor = "multi_thread")] + async fn parses_dev_engines() { + let sandbox = create_empty_proto_sandbox(); + let plugin = sandbox.create_plugin("nub-test").await; + + assert_eq!( + plugin + .parse_version_file(ParseVersionFileInput { + content: r#"{ "devEngines": { "packageManager": { "name": "nub", "version": "1.2.3" } } }"#.into(), + file: "package.json".into(), + ..Default::default() + }) + .await, + ParseVersionFileOutput { + version: Some(UnresolvedVersionSpec::parse("1.2.3").unwrap()), + } + ); + } + + #[tokio::test(flavor = "multi_thread")] + async fn parses_volta() { + let sandbox = create_empty_proto_sandbox(); + let plugin = sandbox.create_plugin("nub-test").await; + + assert_eq!( + plugin + .parse_version_file(ParseVersionFileInput { + content: r#"{ "volta": { "nub": "1.2.3" } }"#.into(), + file: "package.json".into(), + ..Default::default() + }) + .await, + ParseVersionFileOutput { + version: Some(UnresolvedVersionSpec::parse("1.2.3").unwrap()), + } + ); + } + } + mod pnpm { use super::*; From 92f5c1b53ddd102f668a68d932439d3b1294abc3 Mon Sep 17 00:00:00 2001 From: Miles Johnson Date: Sat, 8 Aug 2026 18:35:11 -0700 Subject: [PATCH 37/78] chore: Release --- Cargo.lock | 2 +- backends/cargo/CHANGELOG.md | 2 +- backends/cargo/Cargo.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2cbdd62f..be8bfe63 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -450,7 +450,7 @@ dependencies = [ [[package]] name = "cargo_backend" -version = "0.1.5" +version = "0.1.6" dependencies = [ "backend_common", "extism-pdk", diff --git a/backends/cargo/CHANGELOG.md b/backends/cargo/CHANGELOG.md index 215e01f9..df3787fd 100644 --- a/backends/cargo/CHANGELOG.md +++ b/backends/cargo/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## Unreleased +## 0.1.6 #### 🚀 Updates diff --git a/backends/cargo/Cargo.toml b/backends/cargo/Cargo.toml index 00e3fc93..21902394 100644 --- a/backends/cargo/Cargo.toml +++ b/backends/cargo/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cargo_backend" -version = "0.1.5" +version = "0.1.6" edition = "2024" description = "Cargo backend WASM plugin for proto, for installing CLIs from crates.io." authors = ["Miles Johnson"] From 2a0572dd61944cee3bdc40c6b458fb09ee67aabb Mon Sep 17 00:00:00 2001 From: Miles Johnson Date: Sat, 8 Aug 2026 18:35:52 -0700 Subject: [PATCH 38/78] chore: Release --- Cargo.lock | 2 +- tools/node-depman/CHANGELOG.md | 2 +- tools/node-depman/Cargo.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index be8bfe63..ca9ad372 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3312,7 +3312,7 @@ dependencies = [ [[package]] name = "node_depman_tool" -version = "0.19.1" +version = "0.20.0" dependencies = [ "extism-pdk", "lang_javascript_common", diff --git a/tools/node-depman/CHANGELOG.md b/tools/node-depman/CHANGELOG.md index 724f31b7..e457d320 100644 --- a/tools/node-depman/CHANGELOG.md +++ b/tools/node-depman/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## Unreleased +## 0.20.0 #### 🚀 Updates diff --git a/tools/node-depman/Cargo.toml b/tools/node-depman/Cargo.toml index 69719d09..c03f0449 100644 --- a/tools/node-depman/Cargo.toml +++ b/tools/node-depman/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "node_depman_tool" -version = "0.19.1" +version = "0.20.0" edition = "2024" description = "Node.js dependency managers (npm, pnpm, yarn) WASM plugin for proto." authors = ["Miles Johnson"] From 036dba9877678db54b745495edb7067424a5c903 Mon Sep 17 00:00:00 2001 From: Miles Johnson Date: Sat, 8 Aug 2026 18:40:27 -0700 Subject: [PATCH 39/78] chore: Release --- Cargo.lock | 2 +- tools/node/CHANGELOG.md | 2 +- tools/node/Cargo.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ca9ad372..060e9121 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3354,7 +3354,7 @@ dependencies = [ [[package]] name = "node_tool" -version = "0.17.11" +version = "0.17.12" dependencies = [ "extism-pdk", "lang_javascript_common", diff --git a/tools/node/CHANGELOG.md b/tools/node/CHANGELOG.md index d92de84e..cc8015d9 100644 --- a/tools/node/CHANGELOG.md +++ b/tools/node/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## Unreleased +## 0.17.12 #### 🚀 Updates diff --git a/tools/node/Cargo.toml b/tools/node/Cargo.toml index c5ace170..ba09a343 100644 --- a/tools/node/Cargo.toml +++ b/tools/node/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "node_tool" -version = "0.17.11" +version = "0.17.12" edition = "2024" description = "Node.js WASM plugin for proto." authors = ["Miles Johnson"] From 1cc4ed21ede39567c83612b6bc7be8860af5beac Mon Sep 17 00:00:00 2001 From: Miles Johnson Date: Sat, 8 Aug 2026 19:14:50 -0700 Subject: [PATCH 40/78] fix: Fix nub not being executable. --- tools/node-depman/CHANGELOG.md | 6 ++++++ tools/node-depman/src/proto.rs | 10 ++++++++-- tools/node-depman/tests/metadata_test.rs | 16 ---------------- 3 files changed, 14 insertions(+), 18 deletions(-) diff --git a/tools/node-depman/CHANGELOG.md b/tools/node-depman/CHANGELOG.md index e457d320..f32d344c 100644 --- a/tools/node-depman/CHANGELOG.md +++ b/tools/node-depman/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## Unreleased + +#### 🐞 Fixes + +- Fixed an issue where the `nub` binary wasn't executable. + ## 0.20.0 #### 🚀 Updates diff --git a/tools/node-depman/src/proto.rs b/tools/node-depman/src/proto.rs index 8ce78c85..edfd68e0 100644 --- a/tools/node-depman/src/proto.rs +++ b/tools/node-depman/src/proto.rs @@ -477,8 +477,14 @@ pub fn locate_executables( PackageManager::Nub => { let exe_name = env.os.get_exe_name("bin/nub"); - primary = ExecutableConfig::new_primary(&exe_name); - secondary.insert("nubx".into(), ExecutableConfig::new(exe_name)); + // Nub binaries are not executable by default within the npm package, + // so we need to make them executable on our end! + // https://github.com/nubjs/nub/blob/main/npm/nub/postinstall.js#L22 + primary = ExecutableConfig::new_primary(&exe_name).update_perms(true); + secondary.insert( + "nubx".into(), + ExecutableConfig::new(exe_name).update_perms(true), + ); } PackageManager::Pnpm => { primary = ExecutableConfig::new_primary("shims/pnpm"); diff --git a/tools/node-depman/tests/metadata_test.rs b/tools/node-depman/tests/metadata_test.rs index 733d640e..5b85a83c 100644 --- a/tools/node-depman/tests/metadata_test.rs +++ b/tools/node-depman/tests/metadata_test.rs @@ -20,10 +20,6 @@ mod node_depman_tool { assert_eq!(metadata.name, "npm"); assert!(metadata.lock_options.ignore_os_arch); assert_eq!(metadata.type_of, PluginType::DependencyManager); - assert_eq!( - metadata.plugin_version.unwrap().to_string(), - env!("CARGO_PKG_VERSION") - ); } } @@ -42,10 +38,6 @@ mod node_depman_tool { // Binaries are os/arch specific, so records must be scoped assert!(!metadata.lock_options.ignore_os_arch); assert_eq!(metadata.type_of, PluginType::DependencyManager); - assert_eq!( - metadata.plugin_version.unwrap().to_string(), - env!("CARGO_PKG_VERSION") - ); // Unlike the other package managers, nub does not require Node.js assert!(metadata.requires.is_empty()); @@ -67,10 +59,6 @@ mod node_depman_tool { // v12+ binaries are os/arch specific, so records must be scoped assert!(!metadata.lock_options.ignore_os_arch); assert_eq!(metadata.type_of, PluginType::DependencyManager); - assert_eq!( - metadata.plugin_version.unwrap().to_string(), - env!("CARGO_PKG_VERSION") - ); } } @@ -89,10 +77,6 @@ mod node_depman_tool { // v6+ binaries are os/arch specific, so records must be scoped assert!(!metadata.lock_options.ignore_os_arch); assert_eq!(metadata.type_of, PluginType::DependencyManager); - assert_eq!( - metadata.plugin_version.unwrap().to_string(), - env!("CARGO_PKG_VERSION") - ); } } } From 7569463e57f44abec8037925cc07df0469aebc32 Mon Sep 17 00:00:00 2001 From: Miles Johnson Date: Sat, 8 Aug 2026 19:15:11 -0700 Subject: [PATCH 41/78] chore: Release --- Cargo.lock | 2 +- tools/node-depman/CHANGELOG.md | 2 +- tools/node-depman/Cargo.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 060e9121..2fdba148 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3312,7 +3312,7 @@ dependencies = [ [[package]] name = "node_depman_tool" -version = "0.20.0" +version = "0.20.1" dependencies = [ "extism-pdk", "lang_javascript_common", diff --git a/tools/node-depman/CHANGELOG.md b/tools/node-depman/CHANGELOG.md index f32d344c..78872774 100644 --- a/tools/node-depman/CHANGELOG.md +++ b/tools/node-depman/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## Unreleased +## 0.20.1 #### 🐞 Fixes diff --git a/tools/node-depman/Cargo.toml b/tools/node-depman/Cargo.toml index c03f0449..4ab197a7 100644 --- a/tools/node-depman/Cargo.toml +++ b/tools/node-depman/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "node_depman_tool" -version = "0.20.0" +version = "0.20.1" edition = "2024" description = "Node.js dependency managers (npm, pnpm, yarn) WASM plugin for proto." authors = ["Miles Johnson"] From b728520e855c74990ea48588d90dd7c47723d254 Mon Sep 17 00:00:00 2001 From: Miles Johnson Date: Mon, 10 Aug 2026 15:13:37 -0700 Subject: [PATCH 42/78] new: Update to moon v2.5 APIs. (#179) --- .moon/workspace.yml | 1 + Cargo.lock | 1294 ++++++----------- Cargo.toml | 22 +- crates/extension-common/src/download.rs | 3 +- crates/extension-common/src/lib.rs | 14 - extensions/download/CHANGELOG.md | 6 + extensions/download/src/download_ext.rs | 25 +- extensions/migrate-nx/CHANGELOG.md | 6 + extensions/migrate-turborepo/CHANGELOG.md | 6 + extensions/unpack/CHANGELOG.md | 6 + extensions/unpack/src/unpack_ext.rs | 37 +- toolchains/bun/CHANGELOG.md | 6 + toolchains/deno/CHANGELOG.md | 6 + toolchains/deno/src/tier2.rs | 18 +- toolchains/deno/tests/tier2_test.rs | 28 +- toolchains/go/CHANGELOG.md | 6 + toolchains/go/src/tier1.rs | 4 +- toolchains/go/src/tier2.rs | 37 +- toolchains/go/tests/tier1_test.rs | 16 +- toolchains/go/tests/tier2_test.rs | 83 +- toolchains/javascript/CHANGELOG.md | 6 + toolchains/javascript/src/config.rs | 14 +- toolchains/javascript/src/tier1.rs | 10 +- toolchains/javascript/src/tier2.rs | 34 +- .../javascript/tests/package_json_test.rs | 28 +- .../javascript/tests/tier1_sync_test.rs | 3 +- toolchains/javascript/tests/tier1_test.rs | 9 +- toolchains/javascript/tests/tier2_env_test.rs | 17 +- toolchains/javascript/tests/tier2_test.rs | 204 +-- toolchains/node-depman/CHANGELOG.md | 6 + toolchains/node-depman/tests/tier2_test.rs | 12 +- toolchains/node/CHANGELOG.md | 6 + toolchains/node/src/tier2.rs | 10 +- toolchains/node/tests/tier2_test.rs | 32 +- toolchains/python-pip/CHANGELOG.md | 6 + toolchains/python-poetry/CHANGELOG.md | 6 + toolchains/python-uv/CHANGELOG.md | 6 + toolchains/python/CHANGELOG.md | 6 + toolchains/python/src/managers/pip.rs | 7 +- toolchains/python/src/tier2.rs | 21 +- toolchains/python/tests/tier2_test.rs | 141 +- toolchains/ruby/CHANGELOG.md | 6 + toolchains/ruby/src/tier2.rs | 21 +- toolchains/ruby/tests/tier2_test.rs | 30 +- toolchains/rust/CHANGELOG.md | 6 + toolchains/rust/src/tier1.rs | 17 +- toolchains/rust/src/tier2.rs | 26 +- toolchains/rust/src/tier2_env.rs | 43 +- toolchains/rust/tests/cargo_toml_test.rs | 20 +- toolchains/rust/tests/tier1_test.rs | 35 +- toolchains/rust/tests/tier2_env_test.rs | 81 +- toolchains/rust/tests/tier2_test.rs | 52 +- toolchains/rust/tests/toolchain_toml_test.rs | 10 +- toolchains/system/CHANGELOG.md | 6 + toolchains/typescript/CHANGELOG.md | 6 + toolchains/typescript/src/context.rs | 30 +- toolchains/typescript/src/tier1.rs | 6 +- toolchains/typescript/src/tier2.rs | 2 +- toolchains/typescript/src/tsconfig_json.rs | 9 +- .../typescript/tests/tsconfig_json_test.rs | 30 +- 60 files changed, 1212 insertions(+), 1432 deletions(-) diff --git a/.moon/workspace.yml b/.moon/workspace.yml index 33366e54..9b8c6c88 100644 --- a/.moon/workspace.yml +++ b/.moon/workspace.yml @@ -38,6 +38,7 @@ projects: python-pip-toolchain: toolchains/python-pip python-poetry-toolchain: toolchains/python-poetry python-uv-toolchain: toolchains/python-uv + ruby-toolchain: toolchains/ruby rust-toolchain: toolchains/rust system-toolchain: toolchains/system typescript-toolchain: toolchains/typescript diff --git a/Cargo.lock b/Cargo.lock index 2fdba148..e8617a38 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,9 +4,9 @@ version = 4 [[package]] name = "addr2line" -version = "0.25.1" +version = "0.26.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b5d307320b3181d6d7954e663bd7c774a838b8220fe0593c86d9fb09f498b4b" +checksum = "59317f77929f0e679d39364702289274de2f0f0b22cbf50b2b8cff2169a0b27a" dependencies = [ "gimli", ] @@ -19,9 +19,9 @@ checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" [[package]] name = "aho-corasick" -version = "1.1.4" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +checksum = "c982642fa9e8606056828ee9a8505737230110bb1099153c79efe865c59d12ba" dependencies = [ "memchr", ] @@ -49,9 +49,9 @@ checksum = "e9d4ee0d472d1cd2e28c97dfa124b3d8d992e10eb0a035f33f5d12e3a177ba3b" [[package]] name = "android_system_properties" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +checksum = "ae221649c9976a6f6c56ae1facf410f3ddb33cc661c4b7b61020a912d4237fbc" dependencies = [ "libc", ] @@ -87,12 +87,12 @@ dependencies = [ "backend_common", "extism-pdk", "proto_pdk", - "proto_pdk_test_utils 0.48.0", + "proto_pdk_test_utils", "rustc-hash", "schematic", "serde", - "starbase_sandbox 0.11.1", - "starbase_utils 0.13.8", + "starbase_sandbox", + "starbase_utils 0.14.2", "tokio", ] @@ -127,13 +127,13 @@ dependencies = [ [[package]] name = "async-trait" -version = "0.1.91" +version = "0.1.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae36dc4177970ef04fde5178d3e2429882def40e57a451f919c098f72baa6cec" +checksum = "82f6aeea286b8eb4dd3431a1be1b59d290ace00f5bfd8e2a159bc2a05e2c1667" dependencies = [ "proc-macro2", "quote", - "syn 3.0.2", + "syn 3.0.3", ] [[package]] @@ -150,9 +150,9 @@ checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" [[package]] name = "aws-lc-rs" -version = "1.17.3" +version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00bdb5da18dac48ca2cc7cd4a98e533e8635a58e2361d13a1a4ee3888e0d72f1" +checksum = "ce2b2dcc879c3bae0d371e77c99f2238400ef24ec001394befa67b6e543add9e" dependencies = [ "aws-lc-sys", "untrusted 0.7.1", @@ -161,9 +161,9 @@ dependencies = [ [[package]] name = "aws-lc-sys" -version = "0.43.0" +version = "0.44.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43103168cc76fe62678a375e722fc9cb3a0146159ac5828bc4f0dfd755c2224c" +checksum = "f09fae7be8bb3174e05c6afdb34199e6dc0c7c04ba9fa237b1967adfbde27483" dependencies = [ "cc", "cmake", @@ -191,6 +191,12 @@ version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" +[[package]] +name = "base64" +version = "0.23.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac07cdecf99051d9a5238b80f35af32cdeba5b336e55d957b318b50137e18da5" + [[package]] name = "binstall-tar" version = "0.4.42" @@ -272,11 +278,11 @@ dependencies = [ "lang_javascript_common", "nodejs_package_json", "proto_pdk", - "proto_pdk_test_utils 0.48.0", + "proto_pdk_test_utils", "schematic", "serde", - "starbase_sandbox 0.11.1", - "starbase_utils 0.13.8", + "starbase_sandbox", + "starbase_utils 0.14.2", "tokio", "tool_common", ] @@ -294,8 +300,8 @@ dependencies = [ "schematic", "serde", "serde_json", - "starbase_sandbox 0.11.1", - "starbase_utils 0.13.8", + "starbase_sandbox", + "starbase_utils 0.14.2", "tokio", "toolchain_common", ] @@ -438,13 +444,13 @@ dependencies = [ [[package]] name = "cargo-lock" -version = "11.0.1" +version = "11.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63585cdf8572aa7adf0e30a253f988f2b77233bfac1973d52efb6dd53a75920e" +checksum = "50524592e6bfbb1bf6f94e8184a786f637faeaf1cf37bbe65502b9a2c5c48939" dependencies = [ "semver", "serde", - "toml 0.9.12+spec-1.1.0", + "toml 1.1.4+spec-1.1.0", "url", ] @@ -456,12 +462,12 @@ dependencies = [ "extism-pdk", "lang_rust_common", "proto_pdk", - "proto_pdk_test_utils 0.48.0", + "proto_pdk_test_utils", "rustc-hash", "schematic", "serde", - "starbase_sandbox 0.11.1", - "starbase_utils 0.13.8", + "starbase_sandbox", + "starbase_utils 0.14.2", "tokio", ] @@ -504,9 +510,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.3.0" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c89588d05638b5b4594a3348a2d6c20277e43a7f5c5202b05cc56888475a47b8" +checksum = "5d262e149917187838d5b42777c8253bcb64500067342904e7d429499a6f277e" dependencies = [ "find-msvc-tools", "jobserver", @@ -514,12 +520,6 @@ dependencies = [ "shlex", ] -[[package]] -name = "cesu8" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" - [[package]] name = "cfg-if" version = "1.0.4" @@ -559,9 +559,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.6.3" +version = "4.6.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fb99565819980999fb7b4a1796046a5c949e6d4ff132cf5fadf5a641e20d776" +checksum = "473c7e07f409a8d772161724aa8db6a765a2532a70f9667eeb7b49d3d02fbdca" dependencies = [ "clap_builder", "clap_derive", @@ -569,9 +569,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.6.2" +version = "4.6.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f09628afdcc538b57f3c6341e9c8e9970f18e4a481690a64974d7023bd33548b" +checksum = "7b48fea5a88e9ae728a2dcbedbfc0e730f7d60da42e1cb049a83c9fb8b789889" dependencies = [ "anstyle", "clap_lex", @@ -579,14 +579,14 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.6.3" +version = "4.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32f2392eae7f16557a3d727ef3a12e57b2b2ca6f98566a5f4fb41ffe305df077" +checksum = "d012d2b9d65aca7f18f4d9878a045bc17899bba951561ba5ec3c2ba1eed9a061" dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.119", + "syn 3.0.3", ] [[package]] @@ -616,7 +616,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fa961b519f0b462e3a3b4a34b64d119eeaca1d59af726fe450bbba07a9fc0a1" dependencies = [ - "thiserror 2.0.19", + "thiserror 2.0.20", ] [[package]] @@ -656,21 +656,6 @@ dependencies = [ "static_assertions", ] -[[package]] -name = "compact_str" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9dfdd1c2274d9aa354115b09dc9a901d6c5576818cdf70d14cae2bdb47df00ab" -dependencies = [ - "castaway", - "cfg-if", - "itoa", - "rustversion", - "ryu", - "serde", - "static_assertions", -] - [[package]] name = "compact_str" version = "0.10.0" @@ -786,46 +771,48 @@ dependencies = [ [[package]] name = "cranelift-assembler-x64" -version = "0.128.4" +version = "0.130.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50a04121a197fde2fe896f8e7cac9812fc41ed6ee9c63e1906090f9f497845f6" +checksum = "adc822414b18d1f5b1b33ce1441534e311e62fef86ebb5b9d382af857d0272c9" dependencies = [ "cranelift-assembler-x64-meta", ] [[package]] name = "cranelift-assembler-x64-meta" -version = "0.128.4" +version = "0.130.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a09e699a94f477303820fb2167024f091543d6240783a2d3b01a3f21c42bc744" +checksum = "8c646808b06f4532478d8d6057d74f15c3322f10d995d9486e7dcea405bf521a" dependencies = [ "cranelift-srcgen", ] [[package]] name = "cranelift-bforest" -version = "0.128.4" +version = "0.130.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f07732c662a9755529e332d86f8c5842171f6e98ba4d5976a178043dad838654" +checksum = "7b5996f01a686b2349cdb379083ec5ad3e8cb8767fb2d495d3a4f2ee4163a18d" dependencies = [ "cranelift-entity", + "wasmtime-internal-core", ] [[package]] name = "cranelift-bitset" -version = "0.128.4" +version = "0.130.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18391da761cf362a06def7a7cf11474d79e55801dd34c2e9ba105b33dc0aef88" +checksum = "523fea83273f6a985520f57788809a4de2165794d9ab00fb1254fceb4f5aa00c" dependencies = [ "serde", "serde_derive", + "wasmtime-internal-core", ] [[package]] name = "cranelift-codegen" -version = "0.128.4" +version = "0.130.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b3a09b3042c69810d255aef59ddc3b3e4c0644d1d90ecfd6e3837798cc88a3c" +checksum = "d73d1e372730b5f64ed1a2bd9f01fe4686c8ec14a28034e3084e530c8d951878" dependencies = [ "bumpalo", "cranelift-assembler-x64", @@ -837,7 +824,8 @@ dependencies = [ "cranelift-entity", "cranelift-isle", "gimli", - "hashbrown 0.15.5", + "hashbrown 0.16.1", + "libm", "log", "pulley-interpreter", "regalloc2", @@ -845,14 +833,14 @@ dependencies = [ "serde", "smallvec", "target-lexicon", - "wasmtime-internal-math", + "wasmtime-internal-core", ] [[package]] name = "cranelift-codegen-meta" -version = "0.128.4" +version = "0.130.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75817926ec812241889208d1b190cadb7fedded4592a4bb01b8524babb9e4849" +checksum = "b0319c18165e93dc1ebf78946a8da0b1c341c95b4a39729a69574671639bdb5f" dependencies = [ "cranelift-assembler-x64-meta", "cranelift-codegen-shared", @@ -863,35 +851,36 @@ dependencies = [ [[package]] name = "cranelift-codegen-shared" -version = "0.128.4" +version = "0.130.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "859158f87a59476476eda3884d883c32e08a143cf3d315095533b362a3250a63" +checksum = "9195cd8aeecb55e401aa96b2eaa55921636e8246c127ed7908f7ef7e0d40f270" [[package]] name = "cranelift-control" -version = "0.128.4" +version = "0.130.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03b65a9aec442d715cbf54d14548b8f395476c09cef7abe03e104a378291ab88" +checksum = "8976c2154b74136322befc74222ab5c7249edd7e2604f8cbef2b94975541ffb9" dependencies = [ "arbitrary", ] [[package]] name = "cranelift-entity" -version = "0.128.4" +version = "0.130.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8334c99a7e86060c24028732efd23bac84585770dcb752329c69f135d64f2fc1" +checksum = "6038b3147c7982f4951150d5f96c7c06c1e7214b99d4b4a98607aadf8ded89d1" dependencies = [ "cranelift-bitset", "serde", "serde_derive", + "wasmtime-internal-core", ] [[package]] name = "cranelift-frontend" -version = "0.128.4" +version = "0.130.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43ac6c095aa5b3e845d7ca3461e67e2b65249eb5401477a5ff9100369b745111" +checksum = "4cbd294abe236e23cc3d907b0936226b6a8342db7636daa9c7c72be1e323420e" dependencies = [ "cranelift-codegen", "log", @@ -901,15 +890,15 @@ dependencies = [ [[package]] name = "cranelift-isle" -version = "0.128.4" +version = "0.130.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69d3d992870ed4f0f2e82e2175275cb3a123a46e9660c6558c46417b822c91fa" +checksum = "b5a90b6ed3aba84189352a87badeb93b2126d3724225a42dc67fdce53d1b139c" [[package]] name = "cranelift-native" -version = "0.128.4" +version = "0.130.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee32e36beaf80f309edb535274cfe0349e1c5cf5799ba2d9f42e828285c6b52e" +checksum = "c3ec0cc1a54e22925eacf4fc3dc815f907734d3b377899d19d52bec04863e853" dependencies = [ "cranelift-codegen", "libc", @@ -918,9 +907,9 @@ dependencies = [ [[package]] name = "cranelift-srcgen" -version = "0.128.4" +version = "0.130.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "903adeaf4938e60209a97b53a2e4326cd2d356aab9764a1934630204bae381c9" +checksum = "948865622f87f30907bb46fbb081b235ae63c1896a99a83c26a003305c1fa82d" [[package]] name = "crc32fast" @@ -1179,7 +1168,7 @@ dependencies = [ "deno_semver", "serde", "serde_json", - "thiserror 2.0.19", + "thiserror 2.0.20", ] [[package]] @@ -1195,7 +1184,7 @@ dependencies = [ "monch", "once_cell", "serde", - "thiserror 2.0.19", + "thiserror 2.0.20", "url", ] @@ -1205,10 +1194,10 @@ version = "0.15.11" dependencies = [ "extism-pdk", "proto_pdk", - "proto_pdk_test_utils 0.48.0", + "proto_pdk_test_utils", "schematic", "serde", - "starbase_sandbox 0.11.1", + "starbase_sandbox", "tokio", "tool_common", ] @@ -1226,8 +1215,8 @@ dependencies = [ "schematic", "serde", "serde_json", - "starbase_sandbox 0.11.1", - "starbase_utils 0.13.8", + "starbase_sandbox", + "starbase_utils 0.14.2", "tokio", "toolchain_common", ] @@ -1404,13 +1393,13 @@ dependencies = [ [[package]] name = "displaydoc" -version = "0.2.6" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ac70aa55017e108007fbaf5aa0f54b021c98f92ff8af59d42eda9da96e3dd4f" +checksum = "c6232dd377dcc64799954cbd3a9bb882e9cdc1308ccd87b1c098f1fb2eaf82a8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.119", + "syn 3.0.3", ] [[package]] @@ -1448,7 +1437,7 @@ dependencies = [ "moon_pdk", "moon_pdk_api", "moon_pdk_test_utils", - "starbase_sandbox 0.11.1", + "starbase_sandbox", "tokio", ] @@ -1475,9 +1464,9 @@ dependencies = [ [[package]] name = "either" -version = "1.16.0" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91622ff5e7162018101f2fea40d6ebf4a78bbe5a49736a2020649edf9693679e" +checksum = "9e5e8f6c15a24b9a3ee5efec809ccd006d3b30e8b3bb63c39af737c7f87daa1d" [[package]] name = "embedded-io" @@ -1543,14 +1532,14 @@ dependencies = [ "moon_pdk", "rustc-hash", "serde", - "starbase_utils 0.13.8", + "starbase_utils 0.14.2", ] [[package]] name = "extism" -version = "1.21.0" +version = "1.30.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed8c5859bdab81d2eb4cd963eeacd8031d353b1ffb2fde43ee9179a0d6295120" +checksum = "4b66cd9ac5c64b49c9bac69db3d1b10d8f9386e7caab73489c2f197ba43d5e05" dependencies = [ "anyhow", "async-trait", @@ -1639,12 +1628,6 @@ dependencies = [ "syn 2.0.119", ] -[[package]] -name = "fallible-iterator" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649" - [[package]] name = "fastrand" version = "2.5.0" @@ -1674,9 +1657,9 @@ dependencies = [ [[package]] name = "find-msvc-tools" -version = "0.1.9" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" +checksum = "26b73573e6edcd2af0cdf47bd6cb58f0b3839491263c314eaad1ccf24430e1de" [[package]] name = "fixedbitset" @@ -1712,9 +1695,9 @@ checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "foldhash" -version = "0.1.5" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" +checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" [[package]] name = "foreign-types" @@ -1944,11 +1927,12 @@ dependencies = [ [[package]] name = "gimli" -version = "0.32.3" +version = "0.33.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7" +checksum = "0bf7f043f89559805f8c7cacc432749b2fa0d0a0a9ee46ce47164ed5ba7f126c" dependencies = [ - "fallible-iterator", + "fnv", + "hashbrown 0.16.1", "indexmap", "stable_deref_trait", ] @@ -1961,9 +1945,9 @@ checksum = "e4eba85ea1d0a966a983acd07deee566e67395d2d96b6fb39e62b5a833f1eb0b" [[package]] name = "globset" -version = "0.4.19" +version = "0.4.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e47d37d2ae4464254884b60ab7071be2b876a9c35b696bd018ddcc76847309cd" +checksum = "07c34a9410465b45bd9787443bc7370f37735bad04b0f0cd57ff1a3186c98988" dependencies = [ "aho-corasick", "bstr", @@ -1989,11 +1973,11 @@ version = "0.16.8" dependencies = [ "extism-pdk", "proto_pdk", - "proto_pdk_api 0.33.0", - "proto_pdk_test_utils 0.48.0", + "proto_pdk_api", + "proto_pdk_test_utils", "schematic", "serde", - "starbase_sandbox 0.11.1", + "starbase_sandbox", "tokio", "tool_common", ] @@ -2013,8 +1997,8 @@ dependencies = [ "schematic", "serde", "serde_json", - "starbase_sandbox 0.11.1", - "starbase_utils 0.13.8", + "starbase_sandbox", + "starbase_utils 0.14.2", "tokio", "toolchain_common", ] @@ -2055,12 +2039,13 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.15.5" +version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" dependencies = [ "foldhash", "serde", + "serde_core", ] [[package]] @@ -2094,9 +2079,9 @@ dependencies = [ [[package]] name = "http" -version = "1.4.2" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6970f50e31d6fc17d3fa27329444bfa74e196cf62e95052a3f6fee181dba6425" +checksum = "918d3568bebf352712bc2ef3d46a8bcf1a75b373be6539de198e9105cbbf9ce0" dependencies = [ "bytes", "itoa", @@ -2136,9 +2121,9 @@ dependencies = [ [[package]] name = "http-cache" -version = "1.0.0-alpha.6" +version = "1.0.0-alpha.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d01e0b5d3afe17eadd68dad863adc0715b7ccfa887effbb9ea4de3c7c78c6c44" +checksum = "e5578f9c6c2ad8403802d209d1ffe8a2a15d3f89bfaabca9deaa977947dba241" dependencies = [ "bytes", "cacache", @@ -2151,6 +2136,7 @@ dependencies = [ "log", "pin-project-lite", "postcard", + "redb", "serde", "tokio", "url", @@ -2158,9 +2144,9 @@ dependencies = [ [[package]] name = "http-cache-reqwest" -version = "1.0.0-alpha.6" +version = "1.0.0-alpha.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ff1fa00fd5b0d38c26d5f24f1cf513e286a988b57880c2bf357304669268fa3" +checksum = "8b8eff2dd41dcc485744bd8ce476cf0fe2df289ba8b49c1750b7c887ef7a07b5" dependencies = [ "anyhow", "async-trait", @@ -2217,9 +2203,9 @@ checksum = "140a09c9305e6d5e557e2ed7cbc68e05765a7d4213975b87cb04920689cc6219" [[package]] name = "hybrid-array" -version = "0.4.13" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "818356c5132c1fede50f837ca96afbe78ff42413047f4abb886217845e1b6c8c" +checksum = "707114b52a152fa7bdb290cd7cd5912d9467273b6d74e21b8d81aca1f8533f6b" dependencies = [ "typenum", ] @@ -2440,9 +2426,9 @@ dependencies = [ [[package]] name = "ignore" -version = "0.4.31" +version = "0.4.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f8a7b8211e695a1d0cd91cace480d4d0bd57667ab10277cc412c5f7f4884f83" +checksum = "00b69833ed729dc5aa7d19541d96d6cf8e9137194207a04916d658e43168402f" dependencies = [ "crossbeam-deque", "globset", @@ -2537,9 +2523,9 @@ dependencies = [ [[package]] name = "ipnet" -version = "2.12.0" +version = "2.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" +checksum = "6a756c3fac73139e83f14c2d742155dd2b78d3ee56597b419a0579b7bdd6dd78" [[package]] name = "is_ci" @@ -2597,12 +2583,12 @@ version = "0.1.1" dependencies = [ "extism-pdk", "proto_pdk", - "proto_pdk_api 0.33.0", - "proto_pdk_test_utils 0.48.0", + "proto_pdk_api", + "proto_pdk_test_utils", "schematic", "serde", "serde_json", - "starbase_sandbox 0.11.1", + "starbase_sandbox", "tokio", "tool_common", ] @@ -2627,29 +2613,13 @@ dependencies = [ "serde", "serde_json", "shell-words", - "starbase_sandbox 0.11.1", - "starbase_utils 0.13.8", + "starbase_sandbox", + "starbase_utils 0.14.2", "tokio", "toolchain_common", "yarn-lock-parser", ] -[[package]] -name = "jni" -version = "0.21.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" -dependencies = [ - "cesu8", - "cfg-if", - "combine", - "jni-sys 0.3.1", - "log", - "thiserror 1.0.69", - "walkdir", - "windows-sys 0.45.0", -] - [[package]] name = "jni" version = "0.22.4" @@ -2659,10 +2629,10 @@ dependencies = [ "cfg-if", "combine", "jni-macros", - "jni-sys 0.4.1", + "jni-sys", "log", "simd_cesu8", - "thiserror 2.0.19", + "thiserror 2.0.20", "walkdir", "windows-link", ] @@ -2680,15 +2650,6 @@ dependencies = [ "syn 2.0.119", ] -[[package]] -name = "jni-sys" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41a652e1f9b6e0275df1f15b32661cf0d4b78d4d87ddec5e0c3c20f097433258" -dependencies = [ - "jni-sys 0.4.1", -] - [[package]] name = "jni-sys" version = "0.4.1" @@ -2720,9 +2681,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.103" +version = "0.3.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53b44bfcdb3f8d5837a46dae1ca9660a837176eee74a28b229bc626816589102" +checksum = "0e0c1080212aad755ea003d18543e8768dd432c48819efd73a7bf1e39b7a5a3a" dependencies = [ "cfg-if", "futures-util", @@ -2731,9 +2692,9 @@ dependencies = [ [[package]] name = "json-strip-comments" -version = "3.1.1" +version = "3.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9301b34ecbe81051a62001a2dfa56d906628efdfbc68153e0a4d5eba58181ece" +checksum = "01ed659c5f428031b9c1fb7de6729af2eb491654d4bcd416a5b5b704986b9658" dependencies = [ "memchr", ] @@ -2784,10 +2745,10 @@ name = "lang_javascript_common" version = "0.1.0" dependencies = [ "nodejs_package_json", - "proto_pdk_api 0.33.0", + "proto_pdk_api", "serde", "serde_json", - "starbase_utils 0.13.8", + "starbase_utils 0.14.2", ] [[package]] @@ -2824,24 +2785,24 @@ checksum = "34b357333733e8260735ba5894eb928c02ecc69c78715f01a8019e7fa7f2db4c" [[package]] name = "libc" -version = "0.2.188" +version = "0.2.189" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22053b6a34f84abc97f9129e61334f40174659a1b9bd18c970b83db6a9a6348b" +checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2" [[package]] name = "liblzma" -version = "0.4.7" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45aec2360b3933207e27908049d8e4df4e476b58180afb1e56b2a4fb72efe4ba" +checksum = "2fe0a34ca854fd4f20c07f696fc8675aec78f87d88d29f5e10257a7490a1b2e1" dependencies = [ "liblzma-sys", ] [[package]] name = "liblzma-sys" -version = "0.4.7" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a046c7f353ba30f810545151e04f63545833803f5b86ee3ddf1517247fe560a5" +checksum = "a0dad045e4b1b7b170be4b60b54b780cafb4490165461bac7d1cf7b703f61d5f" dependencies = [ "cc", "libc", @@ -2856,9 +2817,9 @@ checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" [[package]] name = "libredox" -version = "0.1.18" +version = "0.1.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c943259e342f1e06ff2da7a83eabdfe7f92ce10262688dbf1895ff0b3e6e4652" +checksum = "2026a5056764a10b2bf5d56488cba40da507f5493a6a429340e2004d9ed085fa" dependencies = [ "libc", ] @@ -3048,8 +3009,8 @@ dependencies = [ "rustc-hash", "serde", "serde_json", - "starbase_sandbox 0.11.1", - "starbase_utils 0.13.8", + "starbase_sandbox", + "starbase_utils 0.14.2", "tokio", ] @@ -3067,8 +3028,8 @@ dependencies = [ "moon_target", "rustc-hash", "serde", - "starbase_sandbox 0.11.1", - "starbase_utils 0.13.8", + "starbase_sandbox", + "starbase_utils 0.14.2", "tokio", ] @@ -3120,9 +3081,9 @@ checksum = "6bcc6ad3b93f756f2532d29f7c7291b8d246a2c460a99a3611327bb726830014" [[package]] name = "moon_common" -version = "2.0.7" +version = "2.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af0ed007c51e64a32e290e10b3cd7692814d69fb37522b2bfd40439369bd5ffa" +checksum = "bae7ab089aab9153270403e5b7546c5d521e078e2c20b42c8691ae41395d1d96" dependencies = [ "dirs", "miette 7.6.0", @@ -3132,14 +3093,14 @@ dependencies = [ "serde", "starbase_id", "starbase_styles", - "thiserror 2.0.19", + "thiserror 2.0.20", ] [[package]] name = "moon_config" -version = "2.1.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "222902244f1af1e0e6c4124ac7022e46ead76cfe26379cc73a90a0e2dfe22d69" +checksum = "54782d87fbfe5f1de3fdbf22193183c6e226c77f1cdf951e6115c4122477bea0" dependencies = [ "deserialize_untagged_verbose_error", "indexmap", @@ -3150,11 +3111,10 @@ dependencies = [ "rpkl", "rustc-hash", "schematic", - "semver", "serde", "serde_json", - "version_spec 0.10.3", - "warpgate_api 0.17.6", + "version_spec", + "warpgate_api", ] [[package]] @@ -3176,14 +3136,14 @@ dependencies = [ "moon_feature_flags", "serde", "starbase_utils 0.13.8", - "thiserror 2.0.19", + "thiserror 2.0.20", ] [[package]] name = "moon_pdk" -version = "2.0.4" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ebaf7567779b4ebc0f97e59d73615c28ef7124a8fec279ea70ce01202270815" +checksum = "366b913031f8f4fa3383069e7d0b49f7f89f1c1f0f2c3a32b4229748162fc763" dependencies = [ "clap", "extism-pdk", @@ -3194,50 +3154,50 @@ dependencies = [ "rustc-hash", "schematic", "serde", - "warpgate_pdk 0.16.5", + "warpgate_pdk", ] [[package]] name = "moon_pdk_api" -version = "2.0.4" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f26f7e646a6227abd2069633078cc5a896c1e199fb6622ce03f2483b9cb08ffb" +checksum = "17e60b6592bdd68319ce9c0b5874e6cf67b7dd2cbdb4a72ca75de8232a2186b6" dependencies = [ "derive_setters", "moon_common", "moon_config", "moon_project", "moon_task", - "proto_pdk_api 0.31.13", + "proto_pdk_api", "rustc-hash", "schematic", "serde", "serde_json", - "warpgate_api 0.17.6", + "warpgate_api", ] [[package]] name = "moon_pdk_test_utils" -version = "2.0.4" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "734fa3d893e1a09967530bf843e4ddea01e8878584b7c2fc90daeb479436e70a" +checksum = "35f11fa31154e4233527500490a68fe350716e1343cd190834feebfe4582341f" dependencies = [ "extism", "moon_pdk_api", "moon_target", - "proto_core 0.56.4", - "proto_pdk_test_utils 0.44.2", + "proto_core", + "proto_pdk_test_utils", "serde", "serde_json", - "starbase_sandbox 0.11.1", - "warpgate 0.30.5", + "starbase_sandbox", + "warpgate", ] [[package]] name = "moon_project" -version = "2.0.6" +version = "2.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f699261f45112dab6e89e61e013a606f2a68122ede96b28ee13f7ff92c4a2581" +checksum = "ffc394cf3c1729d10c891f946e49b1ab38bf9bba68dd5b9bf0489802c50ed51b" dependencies = [ "miette 7.6.0", "moon_common", @@ -3245,30 +3205,30 @@ dependencies = [ "moon_file_group", "moon_task", "serde", - "thiserror 2.0.19", + "thiserror 2.0.20", ] [[package]] name = "moon_target" -version = "3.0.0" +version = "3.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73fbdd8e17bb29ca8042b62a133211a163ec320536be1c4f2e12a054612c8f1a" +checksum = "d571381d1a0d8db6c87958f035e60e69fa6f5488c977c90bd5b6a917d29ea7d9" dependencies = [ - "compact_str 0.9.1", + "compact_str 0.10.0", "miette 7.6.0", "moon_common", "regex", "schematic", "serde", - "thiserror 2.0.19", + "thiserror 2.0.20", "tracing", ] [[package]] name = "moon_task" -version = "2.1.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "178fe96a4d401f97ab61367332a31bb3994da9074dbbe46349c02b97b180712c" +checksum = "21cebc233793fe0fa563680ee4b9734b9f267f42719bc93143a57be2665e6ad9" dependencies = [ "miette 7.6.0", "moon_common", @@ -3277,7 +3237,7 @@ dependencies = [ "moon_target", "rustc-hash", "serde", - "starbase_utils 0.13.8", + "starbase_utils 0.14.2", ] [[package]] @@ -3286,9 +3246,9 @@ version = "0.4.3" dependencies = [ "extism-pdk", "proto_pdk", - "proto_pdk_test_utils 0.48.0", + "proto_pdk_test_utils", "serde", - "starbase_sandbox 0.11.1", + "starbase_sandbox", "tokio", "tool_common", ] @@ -3319,15 +3279,15 @@ dependencies = [ "nodejs_package_json", "npmrc-config-rs", "proto_pdk", - "proto_pdk_api 0.33.0", - "proto_pdk_test_utils 0.48.0", + "proto_pdk_api", + "proto_pdk_test_utils", "regex", "rustc-hash", "schematic", "serde", "serde_json", - "starbase_sandbox 0.11.1", - "starbase_utils 0.13.8", + "starbase_sandbox", + "starbase_utils 0.14.2", "tokio", "tool_common", ] @@ -3342,12 +3302,12 @@ dependencies = [ "moon_pdk_api", "moon_pdk_test_utils", "node_depman_tool", - "proto_pdk_api 0.33.0", + "proto_pdk_api", "schematic", "serde", "serde_json", - "starbase_sandbox 0.11.1", - "starbase_utils 0.13.8", + "starbase_sandbox", + "starbase_utils 0.14.2", "tokio", "toolchain_common", ] @@ -3360,12 +3320,12 @@ dependencies = [ "lang_javascript_common", "nodejs_package_json", "proto_pdk", - "proto_pdk_test_utils 0.48.0", + "proto_pdk_test_utils", "schematic", "serde", "serial_test", - "starbase_sandbox 0.11.1", - "starbase_utils 0.13.8", + "starbase_sandbox", + "starbase_utils 0.14.2", "tokio", "tool_common", ] @@ -3384,8 +3344,8 @@ dependencies = [ "schematic", "serde", "serde_json", - "starbase_sandbox 0.11.1", - "starbase_utils 0.13.8", + "starbase_sandbox", + "starbase_utils 0.14.2", "tokio", "toolchain_common", ] @@ -3402,7 +3362,7 @@ dependencies = [ "semver", "serde", "serde_json", - "thiserror 2.0.19", + "thiserror 2.0.20", ] [[package]] @@ -3446,12 +3406,12 @@ dependencies = [ "backend_common", "extism-pdk", "proto_pdk", - "proto_pdk_test_utils 0.48.0", + "proto_pdk_test_utils", "rustc-hash", "schematic", "serde", - "starbase_sandbox 0.11.1", - "starbase_utils 0.13.8", + "starbase_sandbox", + "starbase_utils 0.14.2", "tokio", ] @@ -3464,7 +3424,7 @@ dependencies = [ "base64 0.22.1", "dirs", "regex", - "thiserror 2.0.19", + "thiserror 2.0.20", "url", "which", ] @@ -3523,42 +3483,16 @@ dependencies = [ [[package]] name = "object" -version = "0.37.3" +version = "0.38.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff76201f031d8863c38aa7f905eca4f53abbfa15f609db4277d44cd8938f33fe" +checksum = "271638cd5fa9cca89c4c304675ca658efc4e64a66c716b7cfe1afb4b9611dbbc" dependencies = [ "crc32fast", - "hashbrown 0.15.5", + "hashbrown 0.16.1", "indexmap", "memchr", ] -[[package]] -name = "oci-client" -version = "0.16.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b7f8deaffcd3b0e3baf93dddcab3d18b91d46dc37d38a8b170089b234de5bb3" -dependencies = [ - "bytes", - "chrono", - "futures-util", - "http", - "http-auth", - "jsonwebtoken", - "lazy_static", - "oci-spec", - "olpc-cjson", - "regex", - "reqwest", - "serde", - "serde_json", - "sha2 0.10.9", - "thiserror 2.0.19", - "tokio", - "tracing", - "unicase", -] - [[package]] name = "oci-client" version = "0.17.0" @@ -3580,7 +3514,7 @@ dependencies = [ "serde", "serde_json", "sha2 0.11.0", - "thiserror 2.0.19", + "thiserror 2.0.20", "tokio", "tracing", "unicase", @@ -3600,7 +3534,7 @@ dependencies = [ "serde_json", "strum", "strum_macros", - "thiserror 2.0.19", + "thiserror 2.0.20", ] [[package]] @@ -3781,9 +3715,9 @@ checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" [[package]] name = "pest" -version = "2.8.7" +version = "2.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47627dd7305c6a2d6c8c6bcd24c5a4c17dbbf425f4f9c5313e724b38fc9782e9" +checksum = "7df728be843c7070fab6ab7c328c4e9e9d78e23bf749c0669c86ee7ebfa050a2" dependencies = [ "memchr", "ucd-trie", @@ -3791,9 +3725,9 @@ dependencies = [ [[package]] name = "pest_derive" -version = "2.8.7" +version = "2.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b4254325ecad416ab689e27ba51da03ba01a9632bc6e108f5fe7c3c4ad29d58" +checksum = "9e2dd6fc3b26b3462ee188aac870f5a41d398f1cd5e2408d16531bd71c9591fd" dependencies = [ "pest", "pest_generator", @@ -3801,9 +3735,9 @@ dependencies = [ [[package]] name = "pest_generator" -version = "2.8.7" +version = "2.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c4c0e91ead7a8f7acecbca6f003fc2e8282b1dbe2dd9c9d2f16aba42995e0a7" +checksum = "6a7a9205cfb6f596a9e8b689c0a15f9ceb7a1aafae7aaf788150ac65b29975b6" dependencies = [ "pest", "pest_meta", @@ -3814,9 +3748,9 @@ dependencies = [ [[package]] name = "pest_meta" -version = "2.8.7" +version = "2.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9744bc48116fee06334924bb5f2bad41eed5e89bd26e29b0b799f9a3f82c210" +checksum = "85abd351c0de1e8384fc791a0737111a350394937e92b956b743dac12429f57c" dependencies = [ "pest", ] @@ -4012,52 +3946,9 @@ dependencies = [ [[package]] name = "proto_core" -version = "0.56.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34127fc437c3aacbb37ce45db87ce84037f5a35d460f06a927c26f531a4bafaf" -dependencies = [ - "convert_case 0.11.0", - "docker_credential", - "dotenvy", - "indexmap", - "iocraft", - "miette 7.6.0", - "minisign-verify", - "oci-client 0.16.1", - "once_cell", - "proto_pdk_api 0.31.13", - "proto_shim", - "regex", - "reqwest", - "rustc-hash", - "scc", - "schematic", - "semver", - "serde", - "serde_json", - "sha2 0.10.9", - "shell-words", - "starbase_archive 0.13.2", - "starbase_console", - "starbase_shell", - "starbase_styles", - "starbase_utils 0.13.8", - "system_env", - "thiserror 2.0.19", - "tokio", - "toml_edit", - "tracing", - "url", - "uuid", - "version_spec 0.10.3", - "warpgate 0.30.5", -] - -[[package]] -name = "proto_core" -version = "0.60.0" +version = "0.60.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2453dd69084fccfdbff7488d3f9c6fe20b1f7bd00f87069db478a2e87a700b8f" +checksum = "69a33f00c01ec2a7cb6cb33a31976213e095750db566fc7ee3ba001acb8aad4d" dependencies = [ "ai_env", "convert_case 0.11.0", @@ -4067,9 +3958,9 @@ dependencies = [ "iocraft", "miette 7.6.0", "minisign-verify", - "oci-client 0.17.0", + "oci-client", "once_cell", - "proto_pdk_api 0.33.0", + "proto_pdk_api", "proto_shim", "regex", "reqwest", @@ -4079,20 +3970,20 @@ dependencies = [ "serde", "serde_json", "shell-words", - "starbase_archive 0.14.4", + "starbase_archive", "starbase_console", "starbase_shell", "starbase_styles", - "starbase_utils 0.14.1", + "starbase_utils 0.14.2", "system_env", - "thiserror 2.0.19", + "thiserror 2.0.20", "tokio", "toml_edit", "tracing", "url", "uuid", - "version_spec 0.11.2", - "warpgate 0.35.0", + "version_spec", + "warpgate", ] [[package]] @@ -4102,28 +3993,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c6984c7bc56c118dad0f597bfbce17429831b46f27f88c54e2367ff671fbdc3" dependencies = [ "extism-pdk", - "proto_pdk_api 0.33.0", - "rustc-hash", - "serde", - "warpgate_pdk 0.17.0", -] - -[[package]] -name = "proto_pdk_api" -version = "0.31.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50ac6c5862ce4f4b6ac4c6146807b8fb94b12790839005af4a91edf548e97176" -dependencies = [ - "derive_setters", + "proto_pdk_api", "rustc-hash", - "schematic", - "semver", "serde", - "serde_json", - "system_env", - "thiserror 2.0.19", - "version_spec 0.10.3", - "warpgate_api 0.17.6", + "warpgate_pdk", ] [[package]] @@ -4138,24 +4011,9 @@ dependencies = [ "serde", "serde_json", "system_env", - "thiserror 2.0.19", - "version_spec 0.11.2", - "warpgate_api 0.18.0", -] - -[[package]] -name = "proto_pdk_test_utils" -version = "0.44.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05136d0efc40a87d85241a7bcb6a8940b6cf21aa93f72b75ba874a016c6570a3" -dependencies = [ - "extism", - "proto_core 0.56.4", - "proto_pdk_api 0.31.13", - "serde", - "serde_json", - "starbase_sandbox 0.11.1", - "warpgate 0.30.5", + "thiserror 2.0.20", + "version_spec", + "warpgate_api", ] [[package]] @@ -4165,12 +4023,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2876fdc4762a98c8b98d333cca84eaf46c48afb9795a89fdbca29e7163c3ce6" dependencies = [ "extism", - "proto_core 0.60.0", - "proto_pdk_api 0.33.0", + "proto_core", + "proto_pdk_api", "serde", "serde_json", - "starbase_sandbox 0.12.0", - "warpgate 0.35.0", + "starbase_sandbox", + "warpgate", ] [[package]] @@ -4195,21 +4053,21 @@ dependencies = [ [[package]] name = "pulley-interpreter" -version = "41.0.4" +version = "43.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9812652c1feb63cf39f8780cecac154a32b22b3665806c733cd4072547233a4" +checksum = "7ec12fe19a9588315a49fe5704502a9c02d6a198303314b0c7c86123b06d29e5" dependencies = [ "cranelift-bitset", "log", "pulley-macros", - "wasmtime-internal-math", + "wasmtime-internal-core", ] [[package]] name = "pulley-macros" -version = "41.0.4" +version = "43.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56000349b6896e3d44286eb9c330891237f40b27fd43c1ccc84547d0b463cb40" +checksum = "36f7d5ef31ebf1b46cd7e722ffef934e670d7e462f49aa01cde07b9b76dca580" dependencies = [ "proc-macro2", "quote", @@ -4226,7 +4084,7 @@ dependencies = [ "pep440_rs", "pep508_rs", "serde", - "thiserror 2.0.19", + "thiserror 2.0.20", "toml 0.9.12+spec-1.1.0", ] @@ -4248,10 +4106,10 @@ version = "0.1.9" dependencies = [ "extism-pdk", "proto_pdk", - "proto_pdk_test_utils 0.48.0", + "proto_pdk_test_utils", "serde", - "starbase_sandbox 0.11.1", - "starbase_utils 0.13.8", + "starbase_sandbox", + "starbase_utils 0.14.2", "tokio", "toml 1.1.4+spec-1.1.0", "tool_common", @@ -4276,10 +4134,10 @@ version = "0.14.9" dependencies = [ "extism-pdk", "proto_pdk", - "proto_pdk_test_utils 0.48.0", + "proto_pdk_test_utils", "regex", "serde", - "starbase_sandbox 0.11.1", + "starbase_sandbox", "tokio", "tool_common", ] @@ -4301,8 +4159,8 @@ dependencies = [ "schematic", "serde", "serde_json", - "starbase_sandbox 0.11.1", - "starbase_utils 0.13.8", + "starbase_sandbox", + "starbase_utils 0.14.2", "tokio", "toolchain_common", ] @@ -4313,9 +4171,9 @@ version = "0.3.4" dependencies = [ "extism-pdk", "proto_pdk", - "proto_pdk_test_utils 0.48.0", + "proto_pdk_test_utils", "serde", - "starbase_sandbox 0.11.1", + "starbase_sandbox", "tokio", "toml 1.1.4+spec-1.1.0", "tool_common", @@ -4348,7 +4206,7 @@ dependencies = [ "rustc-hash", "rustls", "socket2", - "thiserror 2.0.19", + "thiserror 2.0.20", "tokio", "tracing", "web-time", @@ -4371,7 +4229,7 @@ dependencies = [ "rustls", "rustls-pki-types", "slab", - "thiserror 2.0.19", + "thiserror 2.0.20", "tinyvec", "tracing", "web-time", @@ -4491,6 +4349,15 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "redb" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e925444704b5f17d32bf42f5b6e2df050bceebc3dcd6e71cc73dafe8092e839" +dependencies = [ + "libc", +] + [[package]] name = "redox_syscall" version = "0.5.18" @@ -4519,7 +4386,7 @@ checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" dependencies = [ "getrandom 0.2.17", "libredox", - "thiserror 2.0.19", + "thiserror 2.0.20", ] [[package]] @@ -4536,13 +4403,13 @@ dependencies = [ [[package]] name = "regalloc2" -version = "0.13.5" +version = "0.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08effbc1fa53aaebff69521a5c05640523fab037b34a4a2c109506bc938246fa" +checksum = "757712e8e61590d6d4f5d563483755538b5aa13467837a3b41cd9832509a7f85" dependencies = [ "allocator-api2", "bumpalo", - "hashbrown 0.15.5", + "hashbrown 0.17.1", "log", "rustc-hash", "smallvec", @@ -4562,9 +4429,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.16" +version = "0.4.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fcfdb36bda0c880c5931cdc7a2bcdc8ba4556847b9d912bca70bc94708711ad" +checksum = "ad8553b9b26413251cbf30e620595c7a41b3887f03da04579c0e6b0d6a06b4b2" dependencies = [ "aho-corasick", "memchr", @@ -4614,7 +4481,7 @@ dependencies = [ "quinn", "rustls", "rustls-pki-types", - "rustls-platform-verifier 0.7.0", + "rustls-platform-verifier", "serde", "serde_json", "serde_urlencoded", @@ -4644,7 +4511,7 @@ dependencies = [ "http", "reqwest", "serde", - "thiserror 2.0.19", + "thiserror 2.0.20", "tower-service", ] @@ -4663,7 +4530,7 @@ dependencies = [ "reqwest", "reqwest-middleware", "retry-policies", - "thiserror 2.0.19", + "thiserror 2.0.20", "tokio", "tracing", "wasmtimer", @@ -4744,9 +4611,9 @@ version = "0.2.9" dependencies = [ "extism-pdk", "proto_pdk", - "proto_pdk_test_utils 0.48.0", + "proto_pdk_test_utils", "serde", - "starbase_sandbox 0.11.1", + "starbase_sandbox", "tokio", "tool_common", ] @@ -4766,8 +4633,8 @@ dependencies = [ "schematic", "serde", "serde_json", - "starbase_sandbox 0.11.1", - "starbase_utils 0.13.8", + "starbase_sandbox", + "starbase_utils 0.14.2", "tokio", "toolchain_common", ] @@ -4789,10 +4656,10 @@ dependencies = [ "extism-pdk", "lang_rust_common", "proto_pdk", - "proto_pdk_test_utils 0.48.0", + "proto_pdk_test_utils", "serde", - "starbase_sandbox 0.11.1", - "starbase_utils 0.13.8", + "starbase_sandbox", + "starbase_utils 0.14.2", "tokio", "toml 1.1.4+spec-1.1.0", "tool_common", @@ -4815,8 +4682,8 @@ dependencies = [ "schematic", "serde", "serde_json", - "starbase_sandbox 0.11.1", - "starbase_utils 0.13.8", + "starbase_sandbox", + "starbase_utils 0.14.2", "tokio", "toolchain_common", ] @@ -4880,9 +4747,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.42" +version = "0.23.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c54fcab019b409d04215d3a17cb438fd7fbf192ee61461f20f4fe18704bc138" +checksum = "0283386ce02abc0151e1761d08802dfe86c173b0b494af5cbc086574e453da06" dependencies = [ "aws-lc-rs", "log", @@ -4908,35 +4775,14 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.15.0" +version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "764899a24af3980067ee14bc143654f297b22eaebfe3c7b6b211920a5a59b046" +checksum = "2f4925028c7eb5d1fcdaf196971378ed9d2c1c4efc7dc5d011256f76c99c0a96" dependencies = [ "web-time", "zeroize", ] -[[package]] -name = "rustls-platform-verifier" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d99feebc72bae7ab76ba994bb5e121b8d83d910ca40b36e0921f53becc41784" -dependencies = [ - "core-foundation", - "core-foundation-sys", - "jni 0.21.1", - "log", - "once_cell", - "rustls", - "rustls-native-certs", - "rustls-platform-verifier-android", - "rustls-webpki", - "security-framework", - "security-framework-sys", - "webpki-root-certs", - "windows-sys 0.61.2", -] - [[package]] name = "rustls-platform-verifier" version = "0.7.0" @@ -4945,7 +4791,7 @@ checksum = "26d1e2536ce4f35f4846aa13bff16bd0ff40157cdb14cc056c7b14ba41233ba0" dependencies = [ "core-foundation", "core-foundation-sys", - "jni 0.22.4", + "jni", "log", "once_cell", "rustls", @@ -5005,9 +4851,9 @@ dependencies = [ [[package]] name = "scc" -version = "3.8.5" +version = "3.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f733aa28b85255811ad1358d559fe9a182e39327cf5470140ed9d444de86e6d5" +checksum = "f8af0b99483d1c3e59471d4f0cb58b244169436a8979c889a91a3f697075ea01" dependencies = [ "saa", "sdd", @@ -5028,11 +4874,11 @@ version = "0.18.2" dependencies = [ "extism-pdk", "proto_pdk", - "proto_pdk_test_utils 0.48.0", + "proto_pdk_test_utils", "regex", "serde", "serde_json", - "starbase_sandbox 0.11.1", + "starbase_sandbox", "tokio", "tool_common", ] @@ -5052,14 +4898,13 @@ dependencies = [ "rpkl", "schematic_macros", "schematic_types", - "semver", "serde", "serde-content", "serde_json", "serde_norway", "serde_path_to_error", "starbase_styles", - "thiserror 2.0.19", + "thiserror 2.0.20", "toml 1.1.4+spec-1.1.0", "tracing", ] @@ -5086,7 +4931,6 @@ dependencies = [ "indexmap", "regex", "rpkl", - "semver", "serde", "serde_json", "serde_norway", @@ -5198,7 +5042,7 @@ checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" dependencies = [ "proc-macro2", "quote", - "syn 3.0.2", + "syn 3.0.3", ] [[package]] @@ -5275,9 +5119,9 @@ dependencies = [ [[package]] name = "serial_test" -version = "3.5.0" +version = "4.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "699f4197115b8a7e7ff19c9a315a4bd6fffec26cc4626ef45ecaea389e081c6d" +checksum = "a6df5ed973ad8d834e09f824f9e9f449af6b9a3745f78dec7cc752770bd3bf11" dependencies = [ "futures-executor", "futures-util", @@ -5289,13 +5133,13 @@ dependencies = [ [[package]] name = "serial_test_derive" -version = "3.5.0" +version = "4.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94e153fc76e1c6a068703d6d29c508a0b15c061c4b7e43da59cc097bc342673c" +checksum = "a22144e767da4ddd8416dbf383700542ffd8a5dc493dfecedfe1fe3ad03c98ae" dependencies = [ "proc-macro2", "quote", - "syn 2.0.119", + "syn 3.0.3", ] [[package]] @@ -5353,9 +5197,9 @@ dependencies = [ [[package]] name = "shell-quote" -version = "0.7.2" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb502615975ae2365825521fa1529ca7648fd03ce0b0746604e0683856ecd7e4" +checksum = "6250294ab161cc8ed5d110114cec70d4cec4e0135edc58e7362d094244edf54a" [[package]] name = "shell-words" @@ -5521,26 +5365,6 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" -[[package]] -name = "starbase_archive" -version = "0.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a244a519b24166278276743125b76f958ca00a6cdfb488ab35411beb444801b5" -dependencies = [ - "binstall-tar", - "bzip2", - "flate2", - "liblzma", - "miette 7.6.0", - "rustc-hash", - "starbase_styles", - "starbase_utils 0.13.8", - "thiserror 2.0.19", - "tracing", - "zip", - "zstd", -] - [[package]] name = "starbase_archive" version = "0.14.4" @@ -5554,8 +5378,8 @@ dependencies = [ "miette 7.6.0", "rustc-hash", "starbase_styles", - "starbase_utils 0.14.1", - "thiserror 2.0.19", + "starbase_utils 0.14.2", + "thiserror 2.0.20", "tracing", "zip", "zstd", @@ -5563,9 +5387,9 @@ dependencies = [ [[package]] name = "starbase_console" -version = "0.6.33" +version = "0.6.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6530b0f4bd6cef5c945dcb277cc91f980dac23d322832bc0dfbd2c4457d5f600" +checksum = "fd3e97b059b90ddb6e48168e12034ef5a126eaebe656c0b172529c2b66d65adf" dependencies = [ "crossterm", "iocraft", @@ -5574,7 +5398,7 @@ dependencies = [ "serde", "serde_json", "starbase_styles", - "thiserror 2.0.19", + "thiserror 2.0.20", "tokio", "tracing", ] @@ -5590,21 +5414,7 @@ dependencies = [ "regex", "schematic", "serde", - "thiserror 2.0.19", -] - -[[package]] -name = "starbase_sandbox" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a52a223bc675989a16257e3cc30fd160ba1ac6871596009782758adca038208c" -dependencies = [ - "assert_cmd", - "assert_fs", - "insta", - "predicates", - "pretty_assertions", - "starbase_utils 0.13.8", + "thiserror 2.0.20", ] [[package]] @@ -5618,21 +5428,21 @@ dependencies = [ "insta", "predicates", "pretty_assertions", - "starbase_utils 0.14.1", + "starbase_utils 0.14.2", ] [[package]] name = "starbase_shell" -version = "0.12.7" +version = "0.12.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "429af8cac86fea24c02ec987dc305df7e86458a0b737840c6a3b5c50f6c4fd0d" +checksum = "faa99da498ff199c18eb754d315429157b2cb8ccea3f5685d883a0d4b24ec5c4" dependencies = [ - "base64 0.22.1", + "base64 0.23.1", "miette 7.6.0", "regex", "shell-quote", "sysinfo", - "thiserror 2.0.19", + "thiserror 2.0.20", "tracing", ] @@ -5654,48 +5464,41 @@ version = "0.13.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6f8804a32cd7c654c3321022fc1b05314d7d2fbf6781f8d19d6e4c97488cffd8" dependencies = [ - "async-trait", "dirs", "ec4rs", - "json-strip-comments", "jwalk", "miette 7.6.0", "reflink-copy", - "reqwest", "scc", - "serde", - "serde_json", - "serde_norway", "starbase_styles", - "thiserror 2.0.19", - "tokio", - "toml 1.1.4+spec-1.1.0", + "thiserror 2.0.20", "tracing", - "url", "wax", ] [[package]] name = "starbase_utils" -version = "0.14.1" +version = "0.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "068a9cf0dd7252c656d26ceca3b146c4d3baf13d0a683169952e4badea60925e" +checksum = "879e06a90157702a315c6859585349cd9c5219f2bdf0288e0ac85a3bb5c9234c" dependencies = [ "async-trait", - "base64 0.22.1", + "base64 0.23.1", "dirs", + "ec4rs", "hex", "json-strip-comments", "jwalk", "miette 7.6.0", "reflink-copy", "reqwest", + "scc", "serde", "serde_json", "serde_norway", "sha2 0.11.0", "starbase_styles", - "thiserror 2.0.19", + "thiserror 2.0.20", "tokio", "toml 1.1.4+spec-1.1.0", "tracing", @@ -5771,9 +5574,9 @@ dependencies = [ [[package]] name = "syn" -version = "3.0.2" +version = "3.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a207d6d6a2b7fc470b80443726053f18a2481b7e1eee970597051596567987a3" +checksum = "53e9bae58849f64dfa4f5d5ae372c8341f7305f82a3868709269343628b659a3" dependencies = [ "proc-macro2", "quote", @@ -5841,7 +5644,7 @@ dependencies = [ "serde", "serde_json", "shell-words", - "thiserror 2.0.19", + "thiserror 2.0.20", ] [[package]] @@ -5911,11 +5714,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.19" +version = "2.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09a43598840e33d5b0331f38c5e30d13bb11c11210a4b58f0d9b18a5a5eefcd9" +checksum = "ec86235f5fcc2a73650310756d2ac5b138a5780bbbdfae3eeccec992c435ba4f" dependencies = [ - "thiserror-impl 2.0.19", + "thiserror-impl 2.0.20", ] [[package]] @@ -5931,13 +5734,13 @@ dependencies = [ [[package]] name = "thiserror-impl" -version = "2.0.19" +version = "2.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43cbfe0cf76104d42a574802844187e84a305e531ed54455f11fbde0f10541cd" +checksum = "bc04cd3e1236dd4a98afca4569f2deb3f120e5422a4023be2cb683f8486292af" dependencies = [ "proc-macro2", "quote", - "syn 3.0.2", + "syn 3.0.3", ] [[package]] @@ -5951,9 +5754,9 @@ dependencies = [ [[package]] name = "time" -version = "0.3.54" +version = "0.3.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e1d5e639ff6bab73cb6885cc7e7b1de96c3f32c68ec55f3952614bec1092244" +checksum = "cdb87b95ec50ddfa440816d227a17b2ccbdda963a316a727fda0fc4334f7d134" dependencies = [ "deranged", "num-conv", @@ -6024,13 +5827,13 @@ dependencies = [ [[package]] name = "tokio-macros" -version = "2.7.1" +version = "2.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6328af13490e73a9b4694030fafd93f8c8c6a9dede33e821c3fc63eddf8042ba" +checksum = "78773a2a397f451582ce068015985c33193cf6dea8b74d2a639fe457b2f07b0e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.119", + "syn 3.0.3", ] [[package]] @@ -6055,9 +5858,9 @@ dependencies = [ [[package]] name = "tokio-stream" -version = "0.1.18" +version = "0.1.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32da49809aab5c3bc678af03902d4ccddea2a87d028d86392a4b1560c6906c70" +checksum = "a3d06f0b082ba57c26b79407372e57cf2a1e28124f78e9479fe80322cf53420b" dependencies = [ "futures-core", "pin-project-lite", @@ -6308,8 +6111,8 @@ dependencies = [ "schematic", "serde", "serde_json", - "starbase_sandbox 0.11.1", - "starbase_utils 0.13.8", + "starbase_sandbox", + "starbase_utils 0.14.2", "tokio", "toolchain_common", "typescript_tsconfig_json", @@ -6389,8 +6192,8 @@ dependencies = [ "moon_pdk", "moon_pdk_api", "moon_pdk_test_utils", - "starbase_sandbox 0.11.1", - "starbase_utils 0.13.8", + "starbase_sandbox", + "starbase_utils 0.14.2", "tokio", ] @@ -6426,17 +6229,17 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "ureq" -version = "3.3.0" +version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dea7109cdcd5864d4eeb1b58a1648dc9bf520360d7af16ec26d0a9354bafcfc0" +checksum = "972d7902c8735f2695410b8aed7df6ed12a47394aa1c8d7af49f0497b731a94d" dependencies = [ - "base64 0.22.1", + "base64 0.23.1", "flate2", "log", "percent-encoding", "rustls", "rustls-pki-types", - "rustls-platform-verifier 0.6.2", + "rustls-platform-verifier", "ureq-proto", "utf8-zero", "webpki-roots", @@ -6444,11 +6247,11 @@ dependencies = [ [[package]] name = "ureq-proto" -version = "0.6.0" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e994ba84b0bd1b1b0cf92878b7ef898a5c1760108fe7b6010327e274917a808c" +checksum = "da5f78b09e6941e1a0f2e30e695e4b120377b54d5e0aec11b594bb57b3971613" dependencies = [ - "base64 0.22.1", + "base64 0.23.1", "http", "httparse", "log", @@ -6523,21 +6326,6 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" -[[package]] -name = "version_spec" -version = "0.10.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56820e158475da461ff8492898347d5a162906917658a530da0a6879c503392b" -dependencies = [ - "compact_str 0.9.1", - "human-sort", - "regex", - "schematic", - "semver", - "serde", - "thiserror 2.0.19", -] - [[package]] name = "version_spec" version = "0.11.2" @@ -6551,7 +6339,7 @@ dependencies = [ "regex", "schematic", "serde", - "thiserror 2.0.19", + "thiserror 2.0.20", ] [[package]] @@ -6584,48 +6372,9 @@ dependencies = [ [[package]] name = "warpgate" -version = "0.30.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47a82380b3b99182158aa0ca78247d73a075b136c541bd452f392e332ecbc84f" -dependencies = [ - "async-trait", - "base64 0.22.1", - "compact_str 0.9.1", - "docker_credential", - "extism", - "http-cache-reqwest", - "miette 7.6.0", - "oci-client 0.16.1", - "once_cell", - "regex", - "reqwest", - "reqwest-middleware", - "reqwest-retry", - "rust-netrc", - "rustc-hash", - "scc", - "schematic", - "serde", - "serde_json", - "sha2 0.10.9", - "starbase_archive 0.13.2", - "starbase_shell", - "starbase_styles", - "starbase_utils 0.13.8", - "system_env", - "thiserror 2.0.19", - "tokio", - "tracing", - "ureq", - "url", - "warpgate_api 0.17.6", -] - -[[package]] -name = "warpgate" -version = "0.35.0" +version = "0.35.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecc0be0833cee347fe795c880e4d39a0bb1aadac45c367b6ce029000ebf4d0a3" +checksum = "42e431d2cb9077b3db89462904dbe61cabd246770fd8dde5e7e2cdbbaacdfba0" dependencies = [ "async-trait", "compact_str 0.10.0", @@ -6633,7 +6382,7 @@ dependencies = [ "extism", "http-cache-reqwest", "miette 7.6.0", - "oci-client 0.17.0", + "oci-client", "once_cell", "regex", "reqwest", @@ -6645,41 +6394,24 @@ dependencies = [ "schematic", "serde", "serde_json", - "starbase_archive 0.14.4", + "starbase_archive", "starbase_shell", "starbase_styles", - "starbase_utils 0.14.1", + "starbase_utils 0.14.2", "system_env", - "thiserror 2.0.19", + "thiserror 2.0.20", "tokio", "tracing", "ureq", "url", - "warpgate_api 0.18.0", -] - -[[package]] -name = "warpgate_api" -version = "0.17.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c678ea51aa280e3b4688966daa09b95ebcc7a58d6f5ace3a00708cda7aea62cf" -dependencies = [ - "anyhow", - "derive_setters", - "rustc-hash", - "schematic", - "serde", - "serde_json", - "starbase_id", - "system_env", - "thiserror 2.0.19", + "warpgate_api", ] [[package]] name = "warpgate_api" -version = "0.18.0" +version = "0.18.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87641915cab01cbd66a95c6b8cda696c5ae241f19b49ec9118fdc41621e525db" +checksum = "6e3d85bb888170166b1bf42011090e66b4c8e48d0563ba6279e4be2cedbf710e" dependencies = [ "anyhow", "derive_setters", @@ -6689,18 +6421,7 @@ dependencies = [ "serde_json", "starbase_id", "system_env", - "thiserror 2.0.19", -] - -[[package]] -name = "warpgate_pdk" -version = "0.16.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2897a95cca4ca14002c2407a365aa161a43fe4e4935d626c170e8c34552a22a" -dependencies = [ - "extism-pdk", - "serde", - "warpgate_api 0.17.6", + "thiserror 2.0.20", ] [[package]] @@ -6713,7 +6434,7 @@ dependencies = [ "serde", "tracing", "tracing-subscriber", - "warpgate_api 0.18.0", + "warpgate_api", ] [[package]] @@ -6724,11 +6445,10 @@ checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "wasi-common" -version = "41.0.4" +version = "43.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f49ffbbd04665d04028f66aee8f24ae7a1f46063f59a28fddfa52ca3091754a2" +checksum = "46137f5bcc41a0f002ed14688e463665388a6f3a6662a12a8c315d4b8849791c" dependencies = [ - "anyhow", "async-trait", "bitflags", "cap-fs-ext", @@ -6741,18 +6461,19 @@ dependencies = [ "log", "rustix 1.1.4", "system-interface", - "thiserror 2.0.19", + "thiserror 2.0.20", "tracing", "wasmtime", + "wasmtime-environ", "wiggle", "windows-sys 0.61.2", ] [[package]] name = "wasm-bindgen" -version = "0.2.126" +version = "0.2.127" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b067c0c11094aef6b7a801c1e34a26affafdf3d051dba08456b868789aaf9a4" +checksum = "1b70935747edd64d89de3efa29d73789b806c15798f8e7dca4d8ac356b50ce70" dependencies = [ "cfg-if", "once_cell", @@ -6763,9 +6484,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.76" +version = "0.4.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c62df1340f32221cb9c54d6a27b030e3dba64361d4a95bed55f9aacb44da291d" +checksum = "6b7777d5cc23d0e91404e53ce2d5e8ec7acae3026b16233dba62cd3246457950" dependencies = [ "js-sys", "wasm-bindgen", @@ -6773,9 +6494,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.126" +version = "0.2.127" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "167ce5e579f6bcf889c4f7175a8a5a585de84e8ff93976ce393efa5f2837aab1" +checksum = "77775f8f3f7217702089053b94958f8f54061a3f663417df76e19cbdcca29bc1" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -6783,9 +6504,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.126" +version = "0.2.127" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3997c7839262f4ef12cf90b818d6340c18e80f263f1a94bf157d0ec4420380e" +checksum = "e11d33f857dc2fb11b8bc75aee111aa9cbeb12cd9f25efd3d4c2a3dd4e235284" dependencies = [ "bumpalo", "proc-macro2", @@ -6796,18 +6517,18 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.126" +version = "0.2.127" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc1b4cb0cc549fcf58d7dfc081778139b3d283a081644e833e84682ad71cea24" +checksum = "7ef64dbcc55df09c7e5a46182d181c2cfa3e925f3da937ea764728b4bbb9dcbf" dependencies = [ "unicode-ident", ] [[package]] name = "wasm-compose" -version = "0.243.0" +version = "0.245.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af801b6f36459023eaec63fdbaedad2fd5a4ab7dc74ecc110a8b5d375c5775e4" +checksum = "5fd23d12cc95c451c1306db5bc63075fbebb612bb70c53b4237b1ce5bc178343" dependencies = [ "anyhow", "heck", @@ -6819,29 +6540,29 @@ dependencies = [ "serde_derive", "serde_yaml", "smallvec", - "wasm-encoder 0.243.0", - "wasmparser 0.243.0", + "wasm-encoder 0.245.1", + "wasmparser 0.245.1", "wat", ] [[package]] name = "wasm-encoder" -version = "0.243.0" +version = "0.245.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c55db9c896d70bd9fa535ce83cd4e1f2ec3726b0edd2142079f594fc3be1cb35" +checksum = "3f9dca005e69bf015e45577e415b9af8c67e8ee3c0e38b5b0add5aa92581ed5c" dependencies = [ "leb128fmt", - "wasmparser 0.243.0", + "wasmparser 0.245.1", ] [[package]] name = "wasm-encoder" -version = "0.254.0" +version = "0.255.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09480d646178e5fdd12bb06e812d0af9a3a191dbc9cd697fdc86687beade7393" +checksum = "9b524283fb5df62eec102ed0574838961bdd7ba5ac9c50d38e2756c51c971a42" dependencies = [ "leb128fmt", - "wasmparser 0.254.0", + "wasmparser 0.255.0", ] [[package]] @@ -6859,12 +6580,12 @@ dependencies = [ [[package]] name = "wasmparser" -version = "0.243.0" +version = "0.245.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6d8db401b0528ec316dfbe579e6ab4152d61739cfe076706d2009127970159d" +checksum = "4f08c9adee0428b7bddf3890fc27e015ac4b761cc608c822667102b8bfd6995e" dependencies = [ "bitflags", - "hashbrown 0.15.5", + "hashbrown 0.16.1", "indexmap", "semver", "serde", @@ -6872,9 +6593,9 @@ dependencies = [ [[package]] name = "wasmparser" -version = "0.254.0" +version = "0.255.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5769a29f799fbab136aaf65b4fe5384cd7d93fe6fc9ba0dcb6c8382a1f16e27" +checksum = "e8e329ef4b5d46e73b91d3ac6924417cad55a8cbbf869c199283383427c3320b" dependencies = [ "bitflags", "indexmap", @@ -6883,23 +6604,22 @@ dependencies = [ [[package]] name = "wasmprinter" -version = "0.243.0" +version = "0.245.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb2b6035559e146114c29a909a3232928ee488d6507a1504d8934e8607b36d7b" +checksum = "5f41517a3716fbb8ccf46daa9c1325f760fcbff5168e75c7392288e410b91ac8" dependencies = [ "anyhow", "termcolor", - "wasmparser 0.243.0", + "wasmparser 0.245.1", ] [[package]] name = "wasmtime" -version = "41.0.4" +version = "43.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2a83182bf04af87571b4c642300479501684f26bab5597f68f68cded5b098fd" +checksum = "efb1ed5899dde98357cfdcf647a4614498798719793898245b4b34e663addabf" dependencies = [ "addr2line", - "anyhow", "async-trait", "bitflags", "bumpalo", @@ -6909,8 +6629,6 @@ dependencies = [ "futures", "fxprof-processed-profile", "gimli", - "hashbrown 0.15.5", - "indexmap", "ittapi", "libc", "log", @@ -6930,18 +6648,17 @@ dependencies = [ "target-lexicon", "tempfile", "wasm-compose", - "wasm-encoder 0.243.0", - "wasmparser 0.243.0", + "wasm-encoder 0.245.1", + "wasmparser 0.245.1", "wasmtime-environ", "wasmtime-internal-cache", "wasmtime-internal-component-macro", "wasmtime-internal-component-util", + "wasmtime-internal-core", "wasmtime-internal-cranelift", "wasmtime-internal-fiber", "wasmtime-internal-jit-debug", "wasmtime-internal-jit-icache-coherence", - "wasmtime-internal-math", - "wasmtime-internal-slab", "wasmtime-internal-unwinder", "wasmtime-internal-versioned-export-macros", "wasmtime-internal-winch", @@ -6951,15 +6668,17 @@ dependencies = [ [[package]] name = "wasmtime-environ" -version = "41.0.4" +version = "43.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb201c41aa23a3642365cfb2e4a183573d85127a3c9d528f56b9997c984541ab" +checksum = "4172382dcc785c31d0e862c6780a18f5dd437914d22c4691351f965ef751c821" dependencies = [ "anyhow", "cpp_demangle", + "cranelift-bforest", "cranelift-bitset", "cranelift-entity", "gimli", + "hashbrown 0.16.1", "indexmap", "log", "object", @@ -6968,19 +6687,21 @@ dependencies = [ "semver", "serde", "serde_derive", + "sha2 0.10.9", "smallvec", "target-lexicon", - "wasm-encoder 0.243.0", - "wasmparser 0.243.0", + "wasm-encoder 0.245.1", + "wasmparser 0.245.1", "wasmprinter", "wasmtime-internal-component-util", + "wasmtime-internal-core", ] [[package]] name = "wasmtime-internal-cache" -version = "41.0.4" +version = "43.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb5b3069d1a67ba5969d0eb1ccd7e141367d4e713f4649aa90356c98e8f19bea" +checksum = "4ed398988226d7aa0505ac6bb576e09532ad722d702ec4e66365d78ed695c95f" dependencies = [ "base64 0.22.1", "directories-next", @@ -6998,9 +6719,9 @@ dependencies = [ [[package]] name = "wasmtime-internal-component-macro" -version = "41.0.4" +version = "43.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c924400db7b6ca996fef1b23beb0f41d5c809836b1ec60fc25b4057e2d25d9b" +checksum = "ae5ec9fff073ff13b81732d56a9515d761c245750bcda09093827f84130ebc25" dependencies = [ "anyhow", "proc-macro2", @@ -7013,15 +6734,27 @@ dependencies = [ [[package]] name = "wasmtime-internal-component-util" -version = "41.0.4" +version = "43.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d3f65daf4bf3d74ca2fbbe20af0589c42e2b398a073486451425d94fd4afef4" +checksum = "935d9ab293ba27d1ec9aa7bc1b3a43993dbe961af2a8f23f90a11e1331b4c13f" + +[[package]] +name = "wasmtime-internal-core" +version = "43.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a3820b174f477d2a7083209d1ad5353fcdb11eaea434b2137b8681029460dd3" +dependencies = [ + "anyhow", + "hashbrown 0.16.1", + "libm", + "serde", +] [[package]] name = "wasmtime-internal-cranelift" -version = "41.0.4" +version = "43.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "633e889cdae76829738db0114ab3b02fce51ea4a1cd9675a67a65fce92e8b418" +checksum = "d1679d205caf9766c6aa309d45bb3e7c634d7725e3164404df33824b9f7c4fb7" dependencies = [ "cfg-if", "cranelift-codegen", @@ -7036,19 +6769,19 @@ dependencies = [ "pulley-interpreter", "smallvec", "target-lexicon", - "thiserror 2.0.19", - "wasmparser 0.243.0", + "thiserror 2.0.20", + "wasmparser 0.245.1", "wasmtime-environ", - "wasmtime-internal-math", + "wasmtime-internal-core", "wasmtime-internal-unwinder", "wasmtime-internal-versioned-export-macros", ] [[package]] name = "wasmtime-internal-fiber" -version = "41.0.4" +version = "43.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "deb126adc5d0c72695cfb77260b357f1b81705a0f8fa30b3944e7c2219c17341" +checksum = "f1e505254058be5b0df458d670ee42d9eafe2349d04c1296e9dc01071dc20a85" dependencies = [ "cc", "cfg-if", @@ -7061,9 +6794,9 @@ dependencies = [ [[package]] name = "wasmtime-internal-jit-debug" -version = "41.0.4" +version = "43.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e66ff7f90a8002187691ff6237ffd09f954a0ebb9de8b2ff7f5c62632134120" +checksum = "1c2e05b345f1773e59c20e6ad7298fd6857cdea245023d88bb659c96d8f0ea72" dependencies = [ "cc", "object", @@ -7073,36 +6806,21 @@ dependencies = [ [[package]] name = "wasmtime-internal-jit-icache-coherence" -version = "41.0.4" +version = "43.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b96df23179ae16d54fb3a420f84ffe4383ec9dd06fad3e5bc782f85f66e8e08" +checksum = "b86701b234a4643e3f111869aa792b3a05a06e02d486ee9cb6c04dae16b52dab" dependencies = [ - "anyhow", "cfg-if", "libc", + "wasmtime-internal-core", "windows-sys 0.61.2", ] -[[package]] -name = "wasmtime-internal-math" -version = "41.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86d1380926682b44c383e9a67f47e7a95e60c6d3fa8c072294dab2c7de6168a0" -dependencies = [ - "libm", -] - -[[package]] -name = "wasmtime-internal-slab" -version = "41.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b63cbea1c0192c7feb7c0dfb35f47166988a3742f29f46b585ef57246c65764" - [[package]] name = "wasmtime-internal-unwinder" -version = "41.0.4" +version = "43.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f25c392c7e5fb891a7416e3c34cfbd148849271e8c58744fda875dde4bec4d6a" +checksum = "f63558d801beb83dde9b336eb4ae049019aee26627926edb32cd119d7e4c83cd" dependencies = [ "cfg-if", "cranelift-codegen", @@ -7113,9 +6831,9 @@ dependencies = [ [[package]] name = "wasmtime-internal-versioned-export-macros" -version = "41.0.4" +version = "43.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70f8b9796a3f0451a7b702508b303d654de640271ac80287176de222f187a237" +checksum = "737c4d956fc3a848541a064afb683dd2771132a6b125be5baaf95c4379aa47df" dependencies = [ "proc-macro2", "quote", @@ -7124,16 +6842,16 @@ dependencies = [ [[package]] name = "wasmtime-internal-winch" -version = "41.0.4" +version = "43.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0063e61f1d0b2c20e9cfc58361a6513d074a23c80b417aac3033724f51648a0" +checksum = "f599b79545e3bba0b7913406055ebede5bb0dabee9ba2015ef25a9f4c9f47807" dependencies = [ "cranelift-codegen", "gimli", "log", "object", "target-lexicon", - "wasmparser 0.243.0", + "wasmparser 0.245.1", "wasmtime-environ", "wasmtime-internal-cranelift", "winch-codegen", @@ -7141,9 +6859,9 @@ dependencies = [ [[package]] name = "wasmtime-internal-wit-bindgen" -version = "41.0.4" +version = "43.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "587699ca7cae16b4a234ffcc834f37e75675933d533809919b52975f5609e2ef" +checksum = "2192a77a00b9a67800c2b4e1c70fb6abca79d6b529e53a2ef9dcdcc36090330d" dependencies = [ "anyhow", "bitflags", @@ -7177,24 +6895,24 @@ dependencies = [ [[package]] name = "wast" -version = "254.0.0" +version = "255.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7ed4dfc8f6b9fc38b231065e2cdfbf7359af5ab945990abf09658dcc63c3e32" +checksum = "55ffec530f199bd3d553ac442c13dd108353cad533cad8514bc41e1f1f0fe686" dependencies = [ "bumpalo", "leb128fmt", "memchr", "unicode-width 0.2.2", - "wasm-encoder 0.254.0", + "wasm-encoder 0.255.0", ] [[package]] name = "wat" -version = "1.254.0" +version = "1.255.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7127f7f9b8f127c879991cecd35f494e4628bae1b0874c681414d8d8831e952c" +checksum = "dda82c82e1486c7eed42a0465e544d80fff37abc3b39482e0d34dbaabe2fe5b1" dependencies = [ - "wast 254.0.0", + "wast 255.0.0", ] [[package]] @@ -7208,15 +6926,15 @@ dependencies = [ "nom 7.1.3", "pori", "regex", - "thiserror 2.0.19", + "thiserror 2.0.20", "walkdir", ] [[package]] name = "web-sys" -version = "0.3.103" +version = "0.3.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8622dcb61c0bcc9fffa6938bed81210af2da9a7e4a1a834b2e37a59b6dfb6141" +checksum = "c435338968042f4f59a557f690a253676d47ce13ceb55d70100e7facf6620a30" dependencies = [ "js-sys", "wasm-bindgen", @@ -7261,38 +6979,38 @@ dependencies = [ [[package]] name = "wiggle" -version = "41.0.4" +version = "43.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a69a60bcbe1475c5dc9ec89210ade54823d44f742e283cba64f98f89697c4cec" +checksum = "9c8cfd3db2f05619c6f36f257d84327c11546e28d61e3a1c1220aaad553bc4b0" dependencies = [ - "anyhow", "bitflags", - "thiserror 2.0.19", + "thiserror 2.0.20", "tracing", "wasmtime", + "wasmtime-environ", "wiggle-macro", "witx", ] [[package]] name = "wiggle-generate" -version = "41.0.4" +version = "43.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21f3dc0fd4dcfc7736434bb216179a2147835309abc09bf226736a40d484548f" +checksum = "4bd7a197903e5b4ff5e13aef9c891960d71e92073600ecf4c86c7e795ac1c803" dependencies = [ - "anyhow", "heck", "proc-macro2", "quote", "syn 2.0.119", + "wasmtime-environ", "witx", ] [[package]] name = "wiggle-macro" -version = "41.0.4" +version = "43.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fea2aea744eded58ae092bf57110c27517dab7d5a300513ff13897325c5c5021" +checksum = "6410b86fcec207070d9372b215d3470bad67215e6bbac46981a16999c4abbc28" dependencies = [ "proc-macro2", "quote", @@ -7333,22 +7051,21 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "winch-codegen" -version = "41.0.4" +version = "43.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c55de3ac5b8bd71e5f6c87a9e511dd3ceb194bdb58183c6a7bf21cd8c0e46fbc" +checksum = "52dbb0cf07b0dfe7b7a1ca8efb8f94ba98bd0fb144c411ea1665c78f0449e958" dependencies = [ - "anyhow", "cranelift-assembler-x64", "cranelift-codegen", "gimli", "regalloc2", "smallvec", "target-lexicon", - "thiserror 2.0.19", - "wasmparser 0.243.0", + "thiserror 2.0.20", + "wasmparser 0.245.1", "wasmtime-environ", + "wasmtime-internal-core", "wasmtime-internal-cranelift", - "wasmtime-internal-math", ] [[package]] @@ -7452,22 +7169,13 @@ dependencies = [ "windows-link", ] -[[package]] -name = "windows-sys" -version = "0.45.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" -dependencies = [ - "windows-targets 0.42.2", -] - [[package]] name = "windows-sys" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.52.6", + "windows-targets", ] [[package]] @@ -7476,7 +7184,7 @@ version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" dependencies = [ - "windows-targets 0.52.6", + "windows-targets", ] [[package]] @@ -7488,35 +7196,20 @@ dependencies = [ "windows-link", ] -[[package]] -name = "windows-targets" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" -dependencies = [ - "windows_aarch64_gnullvm 0.42.2", - "windows_aarch64_msvc 0.42.2", - "windows_i686_gnu 0.42.2", - "windows_i686_msvc 0.42.2", - "windows_x86_64_gnu 0.42.2", - "windows_x86_64_gnullvm 0.42.2", - "windows_x86_64_msvc 0.42.2", -] - [[package]] name = "windows-targets" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm 0.52.6", - "windows_aarch64_msvc 0.52.6", - "windows_i686_gnu 0.52.6", + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", "windows_i686_gnullvm", - "windows_i686_msvc 0.52.6", - "windows_x86_64_gnu 0.52.6", - "windows_x86_64_gnullvm 0.52.6", - "windows_x86_64_msvc 0.52.6", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", ] [[package]] @@ -7528,36 +7221,18 @@ dependencies = [ "windows-link", ] -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" - [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" -[[package]] -name = "windows_aarch64_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" - [[package]] name = "windows_aarch64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" -[[package]] -name = "windows_i686_gnu" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" - [[package]] name = "windows_i686_gnu" version = "0.52.6" @@ -7570,48 +7245,24 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" -[[package]] -name = "windows_i686_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" - [[package]] name = "windows_i686_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" -[[package]] -name = "windows_x86_64_gnu" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" - [[package]] name = "windows_x86_64_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" - [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" -[[package]] -name = "windows_x86_64_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" - [[package]] name = "windows_x86_64_msvc" version = "0.52.6" @@ -7645,11 +7296,12 @@ dependencies = [ [[package]] name = "wit-parser" -version = "0.243.0" +version = "0.245.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df983a8608e513d8997f435bb74207bf0933d0e49ca97aa9d8a6157164b9b7fc" +checksum = "330698718e82983499419494dd1e3d7811a457a9bf9f69734e8c5f07a2547929" dependencies = [ "anyhow", + "hashbrown 0.16.1", "id-arena", "indexmap", "log", @@ -7658,7 +7310,7 @@ dependencies = [ "serde_derive", "serde_json", "unicode-xid", - "wasmparser 0.243.0", + "wasmparser 0.245.1", ] [[package]] @@ -7709,7 +7361,7 @@ checksum = "f903203119ded2bc5e8ba635fa8f3c54bddcc174f89d2b39637cf1be5c3f0944" dependencies = [ "nom 8.0.0", "nom-language", - "thiserror 2.0.19", + "thiserror 2.0.20", ] [[package]] @@ -7737,18 +7389,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.55" +version = "0.8.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5a105cd7b140f6eeec8acff2ea38135d3cab283ada58540f629fe51e46696eb" +checksum = "556764e583adb45a9f8d413c2a147fa7e8d821e48e12b14fd560b607998b75eb" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.55" +version = "0.8.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fe976fb70c78cd64cccfe3a6fc142244e8a77b70959b30faf9d0ac37ee228eb" +checksum = "f2ab42fc20575779bd240faa45f94a74256f755c0fa9e89f0ede20d91d0cdfc1" dependencies = [ "proc-macro2", "quote", @@ -7848,9 +7500,9 @@ dependencies = [ [[package]] name = "zlib-rs" -version = "0.6.6" +version = "0.6.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b142a20ec14a91d5bc708c1dc21b080c550113d8aa77afa29635673a65dd02c5" +checksum = "34b31d188d9d685a4f9c7b46d6e36631b07058d2cfe190267adce54dc230bf12" [[package]] name = "zmij" diff --git a/Cargo.toml b/Cargo.toml index 0b880740..8c32a678 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,17 +5,17 @@ members = ["backends/*", "crates/*", "extensions/*", "tools/*", "toolchains/*"] [workspace.dependencies] # Common extism-pdk = { version = "1.4.1" } -regex = { version = "1.11.3", default-features = false, features = ["std"] } +regex = { version = "1.13.1", default-features = false, features = ["std"] } rustc-hash = "2.1.2" schematic = { version = "0.19.7", default-features = false, features = [ "schema", ] } serde = { version = "1.0.228", features = ["derive"] } serde_json = { version = "1.0.150", features = ["preserve_order"] } -serial_test = "3.5.0" +serial_test = "4.0.1" shell-words = "1.1.1" -starbase_sandbox = "0.11.0" -starbase_utils = { version = "0.13.6", default-features = false, features = [ +starbase_sandbox = "0.12.0" +starbase_utils = { version = "0.14.2", default-features = false, features = [ "editor-config", ] } tokio = { version = "1.52.3", features = ["full"] } @@ -25,13 +25,13 @@ toml = { version = "1.1.4", default-features = false, features = [ ] } # moon -moon_common = { version = "2.0.7" } -moon_config = { version = "2.1.0" } -moon_pdk = { version = "2.0.4" } -moon_pdk_api = { version = "2.0.4" } -moon_pdk_test_utils = { version = "2.0.4" } -moon_project = { version = "2.0.6" } -moon_target = { version = "3.0.0" } +moon_common = { version = "2.0.8" } +moon_config = { version = "2.1.1" } +moon_pdk = { version = "2.1.0" } +moon_pdk_api = { version = "2.1.0" } +moon_pdk_test_utils = { version = "2.1.0" } +moon_project = { version = "2.0.7" } +moon_target = { version = "3.0.1" } # moon_common = { path = "../../moon/crates/common" } # moon_config = { path = "../../moon/crates/config" } # moon_pdk = { path = "../../moon/crates/pdk" } diff --git a/crates/extension-common/src/download.rs b/crates/extension-common/src/download.rs index 6e7ede65..4bd41926 100644 --- a/crates/extension-common/src/download.rs +++ b/crates/extension-common/src/download.rs @@ -1,4 +1,3 @@ -use crate::format_virtual_path; use extism_pdk::debug; use moon_pdk::{AnyResult, VirtualPath, fetch_bytes}; use starbase_utils::fs; @@ -25,7 +24,7 @@ pub fn download_from_url, P: AsRef>( fs::create_dir_all(dir)?; fs::write_file(&file, bytes)?; - debug!("Downloaded to {}", format_virtual_path(&file)); + debug!("Downloaded to {file}"); Ok(file) } diff --git a/crates/extension-common/src/lib.rs b/crates/extension-common/src/lib.rs index 7ab9af84..646ea766 100644 --- a/crates/extension-common/src/lib.rs +++ b/crates/extension-common/src/lib.rs @@ -3,17 +3,3 @@ pub mod migrator; pub mod project_graph; pub use common::*; -use moon_pdk::VirtualPath; -use std::borrow::Cow; - -pub fn format_virtual_path(path: &VirtualPath) -> Cow<'_, str> { - if let Some(real) = path.real_path_string() { - Cow::Owned(real) - } else if let Some(rel) = path.without_prefix() { - rel.to_string_lossy() - } else if let Some(virt) = path.virtual_path_string() { - Cow::Owned(virt) - } else { - Cow::Owned(path.to_string()) - } -} diff --git a/extensions/download/CHANGELOG.md b/extensions/download/CHANGELOG.md index 1a5e7b9b..9e95a83e 100644 --- a/extensions/download/CHANGELOG.md +++ b/extensions/download/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## Unreleased + +#### 🚀 Updates + +- Updated to support moon v2.5 release. + ## 1.0.2 #### 🚀 Updates diff --git a/extensions/download/src/download_ext.rs b/extensions/download/src/download_ext.rs index 05b6c4ff..c21215bd 100644 --- a/extensions/download/src/download_ext.rs +++ b/extensions/download/src/download_ext.rs @@ -1,5 +1,5 @@ use extension_common::download::download_from_url; -use extension_common::{enable_tracing, format_virtual_path}; +use extension_common::enable_tracing; use extism_pdk::*; use moon_pdk::*; use moon_pdk_api::{ExecuteExtensionInput, RegisterExtensionInput, RegisterExtensionOutput}; @@ -7,7 +7,6 @@ use moon_pdk_api::{ExecuteExtensionInput, RegisterExtensionInput, RegisterExtens #[host_fn] extern "ExtismHost" { fn host_log(input: Json); - fn to_virtual_path(path: String) -> String; } #[plugin_fn] @@ -46,34 +45,24 @@ pub fn execute_extension(Json(input): Json) -> FnResult<( // Determine destination directory debug!("Determining destination directory"); - let dest_dir = into_virtual_path( - input - .context - .get_absolute_path(args.dest.as_deref().unwrap_or_default()), - )?; + let dest_dir = input + .context + .get_absolute_path(args.dest.as_deref().unwrap_or_default()); if dest_dir.exists() && dest_dir.is_file() { return Err(plugin_err!( - "Destination {} must be a directory, found a file.", - format_virtual_path(&dest_dir), + "Destination {dest_dir} must be a directory, found a file.", )); } - debug!( - "Destination {} will be used", - format_virtual_path(&dest_dir), - ); + debug!("Destination {dest_dir} will be used",); // Attempt to download the file host_log!(stdout, "Downloading {}", args.url); let dest_file = download_from_url(&args.url, &dest_dir, args.name.as_deref())?; - host_log!( - stdout, - "Downloaded to {}", - format_virtual_path(&dest_file), - ); + host_log!(stdout, "Downloaded to {dest_file}",); Ok(()) } diff --git a/extensions/migrate-nx/CHANGELOG.md b/extensions/migrate-nx/CHANGELOG.md index 5a1767d7..b3e257b4 100644 --- a/extensions/migrate-nx/CHANGELOG.md +++ b/extensions/migrate-nx/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## Unreleased + +#### 🚀 Updates + +- Updated to support moon v2.5 release. + ## 1.0.3 #### 🚀 Updates diff --git a/extensions/migrate-turborepo/CHANGELOG.md b/extensions/migrate-turborepo/CHANGELOG.md index 244763f6..47c52050 100644 --- a/extensions/migrate-turborepo/CHANGELOG.md +++ b/extensions/migrate-turborepo/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## Unreleased + +#### 🚀 Updates + +- Updated to support moon v2.5 release. + ## 1.0.3 #### 🚀 Updates diff --git a/extensions/unpack/CHANGELOG.md b/extensions/unpack/CHANGELOG.md index 9535ce18..a27bc0c6 100644 --- a/extensions/unpack/CHANGELOG.md +++ b/extensions/unpack/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## Unreleased + +#### 🚀 Updates + +- Updated to support moon v2.5 release. + ## 1.0.2 #### 🚀 Updates diff --git a/extensions/unpack/src/unpack_ext.rs b/extensions/unpack/src/unpack_ext.rs index 7a7f0535..92b9ed0e 100644 --- a/extensions/unpack/src/unpack_ext.rs +++ b/extensions/unpack/src/unpack_ext.rs @@ -1,5 +1,5 @@ use extension_common::download::download_from_url; -use extension_common::{enable_tracing, format_virtual_path}; +use extension_common::enable_tracing; use extism_pdk::*; use moon_pdk::*; use moon_pdk_api::{ExecuteExtensionInput, RegisterExtensionInput, RegisterExtensionOutput}; @@ -8,7 +8,6 @@ use starbase_utils::fs; #[host_fn] extern "ExtismHost" { fn host_log(input: Json); - fn to_virtual_path(path: String) -> String; } #[plugin_fn] @@ -45,7 +44,7 @@ pub fn execute_extension(Json(input): Json) -> FnResult<( let src_file = if args.src.starts_with("http") { debug!("Received a URL as the input source"); - download_from_url(&args.src, virtual_path!("/moon/temp"), None)? + download_from_url(&args.src, VirtualPath::new("/moon/temp"), None)? } else { debug!( "Converting source {} to an absolute virtual path", @@ -57,8 +56,7 @@ pub fn execute_extension(Json(input): Json) -> FnResult<( if !src_file.exists() || !src_file.is_file() { return Err(plugin_err!( - "Source {} must be a valid file.", - format_virtual_path(&src_file), + "Source {src_file} must be a valid file.", )); } @@ -69,16 +67,11 @@ pub fn execute_extension(Json(input): Json) -> FnResult<( if dest_dir.exists() && dest_dir.is_file() { return Err(plugin_err!( - "Destination {} must be a directory, found a file.", - format_virtual_path(&dest_dir), + "Destination {dest_dir} must be a directory, found a file.", )); } - host_log!( - stdout, - "Unpacking archive to {}", - format_virtual_path(&dest_dir), - ); + host_log!(stdout, "Unpacking archive to {dest_dir}",); fs::create_dir_all(&dest_dir)?; @@ -86,9 +79,15 @@ pub fn execute_extension(Json(input): Json) -> FnResult<( exec_streamed( "unzip", [ - src_file.real_path_string().expect("Invalid source"), + src_file + .to_real_path()? + .expect("Invalid source") + .to_string(), "-d".into(), - dest_dir.real_path_string().expect("Invalid destination"), + dest_dir + .to_real_path()? + .expect("Invalid destination") + .to_string(), ], )?; } else { @@ -96,9 +95,15 @@ pub fn execute_extension(Json(input): Json) -> FnResult<( "tar", [ "-xf".into(), - src_file.real_path_string().expect("Invalid source"), + src_file + .to_real_path()? + .expect("Invalid source") + .to_string(), "-C".into(), - dest_dir.real_path_string().expect("Invalid destination"), + dest_dir + .to_real_path()? + .expect("Invalid destination") + .to_string(), ], )?; } diff --git a/toolchains/bun/CHANGELOG.md b/toolchains/bun/CHANGELOG.md index c7510123..3131cc7d 100644 --- a/toolchains/bun/CHANGELOG.md +++ b/toolchains/bun/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## Unreleased + +#### 🚀 Updates + +- Updated to support moon v2.5 release. + ## 1.0.2 #### 🚀 Updates diff --git a/toolchains/deno/CHANGELOG.md b/toolchains/deno/CHANGELOG.md index 66efc014..1b7d9c59 100644 --- a/toolchains/deno/CHANGELOG.md +++ b/toolchains/deno/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## Unreleased + +#### 🚀 Updates + +- Updated to support moon v2.5 release. + ## 1.1.0 #### 🚀 Updates diff --git a/toolchains/deno/src/tier2.rs b/toolchains/deno/src/tier2.rs index 16668e4f..83470ab0 100644 --- a/toolchains/deno/src/tier2.rs +++ b/toolchains/deno/src/tier2.rs @@ -5,7 +5,8 @@ use crate::deno_json::DenoJson; use extism_pdk::*; use moon_config::BinEntry; use moon_pdk::{ - get_host_env_var, get_host_environment, parse_toolchain_config, parse_toolchain_config_schema, + VirtualPathExt, get_host_env_var, get_host_environment, parse_toolchain_config, + parse_toolchain_config_schema, }; use moon_pdk_api::*; use std::path::PathBuf; @@ -16,7 +17,7 @@ fn gather_shared_paths( paths: &mut Vec, ) -> AnyResult<()> { if let Some(globals_dir) = globals_dir - && globals_dir.real_path().is_some() + && globals_dir.to_real_path()?.is_some() { // Avoid the host env overhead if we already // have a valid globals directory! @@ -31,7 +32,11 @@ fn gather_shared_paths( } else if let Some(value) = get_host_env_var("DENO_HOME")? { Some(PathBuf::from(value).join("bin")) } else { - env.home_dir.join(".deno").join("bin").real_path() + env.home_dir + .join(".deno") + .join("bin") + .to_real_path()? + .map(|path| path.to_path_buf()) }; if let Some(dir) = maybe_dir { @@ -58,7 +63,7 @@ pub fn extend_task_command( output.args = Some(Extend::Prepend(config.execute_args)); } - gather_shared_paths(&env, input.globals_dir.as_ref(), &mut output.paths)?; + gather_shared_paths(env, input.globals_dir.as_ref(), &mut output.paths)?; Ok(Json(output)) } @@ -70,7 +75,7 @@ pub fn extend_task_script( let mut output = ExtendTaskScriptOutput::default(); let env = get_host_environment()?; - gather_shared_paths(&env, input.globals_dir.as_ref(), &mut output.paths)?; + gather_shared_paths(env, input.globals_dir.as_ref(), &mut output.paths)?; Ok(Json(output)) } @@ -165,7 +170,8 @@ pub fn setup_environment( output.commands.push( ExecCommand::new(ExecCommandInput::new("deno", args).cwd(input.root.to_owned())) - .cache(format!("deno-bin-{name}")), + .cache(CacheStrategy::Memory) + .label(format!("deno-bin-{name}")), ); } } diff --git a/toolchains/deno/tests/tier2_test.rs b/toolchains/deno/tests/tier2_test.rs index 0aef8863..e2774366 100644 --- a/toolchains/deno/tests/tier2_test.rs +++ b/toolchains/deno/tests/tier2_test.rs @@ -57,7 +57,7 @@ mod deno_toolchain_tier2 { let output = plugin .extend_task_command(ExtendTaskCommandInput { command: "unknown".into(), - globals_dir: Some(VirtualPath::Real(sandbox.path().into())), + globals_dir: Some(VirtualPath::new(sandbox.path())), ..Default::default() }) .await; @@ -126,7 +126,7 @@ mod deno_toolchain_tier2 { let output = plugin .parse_manifest(ParseManifestInput { - path: VirtualPath::Real(sandbox.path().join("deno.json")), + path: VirtualPath::new(sandbox.path().join("deno.json")), ..Default::default() }) .await; @@ -181,7 +181,7 @@ mod deno_toolchain_tier2 { let output = plugin .parse_manifest(ParseManifestInput { - path: VirtualPath::Real(sandbox.path().join("deno-publish.json")), + path: VirtualPath::new(sandbox.path().join("deno-publish.json")), ..Default::default() }) .await; @@ -200,7 +200,7 @@ mod deno_toolchain_tier2 { let output = plugin .setup_environment(SetupEnvironmentInput { - root: VirtualPath::Real(sandbox.path().into()), + root: VirtualPath::new(sandbox.path()), toolchain_config: json!({ "bins": [] }), @@ -218,7 +218,7 @@ mod deno_toolchain_tier2 { let output = plugin .setup_environment(SetupEnvironmentInput { - root: VirtualPath::Real(sandbox.path().into()), + root: VirtualPath::new(sandbox.path()), toolchain_config: json!({ "bins": [ "jsr:@std/http/file-server", @@ -247,7 +247,8 @@ mod deno_toolchain_tier2 { ) .cwd(plugin.plugin.to_virtual_path(sandbox.path())) ) - .cache("deno-bin-jsr:@std/http/file-server"), + .cache(CacheStrategy::Memory) + .label("deno-bin-jsr:@std/http/file-server"), ExecCommand::new( ExecCommandInput::new( "deno", @@ -261,7 +262,8 @@ mod deno_toolchain_tier2 { ) .cwd(plugin.plugin.to_virtual_path(sandbox.path())) ) - .cache("deno-bin-https://examples.deno.land/color-logging.ts"), + .cache(CacheStrategy::Memory) + .label("deno-bin-https://examples.deno.land/color-logging.ts"), ] ); } @@ -273,7 +275,7 @@ mod deno_toolchain_tier2 { let output = plugin .setup_environment(SetupEnvironmentInput { - root: VirtualPath::Real(sandbox.path().into()), + root: VirtualPath::new(sandbox.path()), toolchain_config: json!({ "bins": [ { @@ -303,7 +305,8 @@ mod deno_toolchain_tier2 { ) .cwd(plugin.plugin.to_virtual_path(sandbox.path())) ) - .cache("deno-bin-jsr:@std/http/file-server")] + .cache(CacheStrategy::Memory) + .label("deno-bin-jsr:@std/http/file-server")] ); } @@ -314,7 +317,7 @@ mod deno_toolchain_tier2 { let output = plugin .setup_environment(SetupEnvironmentInput { - root: VirtualPath::Real(sandbox.path().into()), + root: VirtualPath::new(sandbox.path()), toolchain_config: json!({ "bins": [ { @@ -343,7 +346,8 @@ mod deno_toolchain_tier2 { ) .cwd(plugin.plugin.to_virtual_path(sandbox.path())) ) - .cache("deno-bin-jsr:@std/http/file-server")] + .cache(CacheStrategy::Memory) + .label("deno-bin-jsr:@std/http/file-server")] ); } @@ -361,7 +365,7 @@ mod deno_toolchain_tier2 { let output = plugin .setup_environment(SetupEnvironmentInput { - root: VirtualPath::Real(sandbox.path().into()), + root: VirtualPath::new(sandbox.path()), toolchain_config: json!({ "bins": [ { diff --git a/toolchains/go/CHANGELOG.md b/toolchains/go/CHANGELOG.md index eb0ec3e4..74f16a91 100644 --- a/toolchains/go/CHANGELOG.md +++ b/toolchains/go/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## Unreleased + +#### 🚀 Updates + +- Updated to support moon v2.5 release. + ## 1.4.4 #### 🐞 Fixes diff --git a/toolchains/go/src/tier1.rs b/toolchains/go/src/tier1.rs index d5eea26c..84cecac5 100644 --- a/toolchains/go/src/tier1.rs +++ b/toolchains/go/src/tier1.rs @@ -86,9 +86,7 @@ pub fn prune_docker(Json(input): Json) -> FnResult(input.toolchain_config)?; let env = get_host_environment()?; - let go_exists = command_exists(&env, "go"); + let go_exists = command_exists(env, "go"); // First pass, gather all packages and their manifests let mut packages = BTreeMap::default(); @@ -95,9 +95,7 @@ pub fn extend_project_graph( let go_mod_path = project_root.join("go.mod"); let mut manifest = if go_mod_path.exists() { - if let Some(file) = go_mod_path.virtual_path() { - output.input_files.push(file); - } + output.input_files.push(go_mod_path.clone()); parse_go_mod(fs::read_file(&go_mod_path)?)? } else { @@ -179,7 +177,7 @@ fn gather_shared_paths( paths: &mut Vec, ) -> AnyResult<()> { if let Some(globals_dir) = globals_dir - && globals_dir.real_path().is_some() + && globals_dir.to_real_path()?.is_some() { // Avoid the host env overhead if we already // have a valid globals directory! @@ -194,7 +192,11 @@ fn gather_shared_paths( } else if let Some(value) = get_host_env_var("GOPATH")? { Some(PathBuf::from(value).join("bin")) } else { - env.home_dir.join("go").join("bin").real_path() + env.home_dir + .join("go") + .join("bin") + .to_real_path()? + .map(|path| path.to_path_buf()) }; if let Some(dir) = maybe_dir { @@ -217,7 +219,7 @@ pub fn extend_task_command( let env = get_host_environment()?; // Always include Go specific paths for all commands - gather_shared_paths(&env, input.globals_dir.as_ref(), &mut output.paths)?; + gather_shared_paths(env, input.globals_dir.as_ref(), &mut output.paths)?; Ok(Json(output)) } @@ -230,7 +232,7 @@ pub fn extend_task_script( let env = get_host_environment()?; // Always include Go specific paths for all commands - gather_shared_paths(&env, input.globals_dir.as_ref(), &mut output.paths)?; + gather_shared_paths(env, input.globals_dir.as_ref(), &mut output.paths)?; Ok(Json(output)) } @@ -252,21 +254,17 @@ pub fn locate_dependencies_root( output.members = Some(go_work.modules); } - output.root = root.virtual_path(); + output.root = Some(root); } // Then `go.sum` second - if output.root.is_none() - && let Some(root) = locate_root(&input.starting_dir, "go.sum") - { - output.root = root.virtual_path(); + if output.root.is_none() { + output.root = locate_root(&input.starting_dir, "go.sum"); } // Otherwise assume `go.mod` - if output.root.is_none() - && let Some(root) = locate_root(&input.starting_dir, "go.mod") - { - output.root = root.virtual_path(); + if output.root.is_none() { + output.root = locate_root(&input.starting_dir, "go.mod"); } Ok(Json(output)) @@ -402,7 +400,8 @@ pub fn setup_environment( output.commands.push( ExecCommand::new(ExecCommandInput::new("go", args).cwd(input.root.to_owned())) - .cache(format!("go-bins-{version}")), + .cache(CacheStrategy::Memory) + .label(format!("go-bins-{version}")), ); } } diff --git a/toolchains/go/tests/tier1_test.rs b/toolchains/go/tests/tier1_test.rs index a0e2132d..76ac5341 100644 --- a/toolchains/go/tests/tier1_test.rs +++ b/toolchains/go/tests/tier1_test.rs @@ -2,7 +2,6 @@ use moon_config::DockerPruneConfig; use moon_pdk_api::*; use moon_pdk_test_utils::create_empty_moon_sandbox; use serde_json::json; -use std::path::PathBuf; mod go_toolchain_tier1 { use super::*; @@ -51,7 +50,7 @@ mod go_toolchain_tier1 { delete_vendor_directories: true, ..Default::default() }, - root: VirtualPath::Real(sandbox.path().into()), + root: VirtualPath::new(sandbox.path()), ..Default::default() }) .await; @@ -72,7 +71,7 @@ mod go_toolchain_tier1 { delete_vendor_directories: false, ..Default::default() }, - root: VirtualPath::Real(sandbox.path().into()), + root: VirtualPath::new(sandbox.path()), ..Default::default() }) .await; @@ -95,14 +94,17 @@ mod go_toolchain_tier1 { delete_vendor_directories: true, ..Default::default() }, - root: VirtualPath::Real(sandbox.path().into()), + root: VirtualPath::new(sandbox.path()), ..Default::default() }) .await; assert!(!sandbox.path().join("vendor").exists()); - assert_eq!(output.changed_files, [PathBuf::from("/workspace/vendor")]); + assert_eq!( + output.changed_files, + [VirtualPath::new("/workspace/vendor")] + ); } #[tokio::test(flavor = "multi_thread")] @@ -118,7 +120,7 @@ mod go_toolchain_tier1 { delete_vendor_directories: true, ..Default::default() }, - root: VirtualPath::Real(sandbox.path().into()), + root: VirtualPath::new(sandbox.path()), toolchain_config: json!({ "vendorDir": "nested/.vendor" }), @@ -130,7 +132,7 @@ mod go_toolchain_tier1 { assert_eq!( output.changed_files, - [PathBuf::from("/workspace/nested/.vendor")] + [VirtualPath::new("/workspace/nested/.vendor")] ); } } diff --git a/toolchains/go/tests/tier2_test.rs b/toolchains/go/tests/tier2_test.rs index 6826f047..c7a4a588 100644 --- a/toolchains/go/tests/tier2_test.rs +++ b/toolchains/go/tests/tier2_test.rs @@ -5,7 +5,6 @@ use moon_pdk_test_utils::{create_empty_moon_sandbox, create_moon_sandbox}; use serde_json::json; use std::collections::BTreeMap; use std::env; -use std::path::PathBuf; mod go_toolchain_tier2 { use super::*; @@ -67,9 +66,9 @@ mod go_toolchain_tier2 { assert_eq!( output.input_files, [ - PathBuf::from("/workspace/a/go.mod"), - PathBuf::from("/workspace/b/go.mod"), - PathBuf::from("/workspace/c/go.mod"), + VirtualPath::new("/workspace/a/go.mod"), + VirtualPath::new("/workspace/b/go.mod"), + VirtualPath::new("/workspace/c/go.mod"), ] ); } @@ -95,7 +94,10 @@ mod go_toolchain_tier2 { ),]) ); - assert_eq!(output.input_files, [PathBuf::from("/workspace/a/go.mod")]); + assert_eq!( + output.input_files, + [VirtualPath::new("/workspace/a/go.mod")] + ); } #[tokio::test(flavor = "multi_thread")] @@ -174,9 +176,9 @@ mod go_toolchain_tier2 { assert_eq!( output.input_files, [ - PathBuf::from("/workspace/a/go.mod"), - PathBuf::from("/workspace/b/go.mod"), - PathBuf::from("/workspace/c/go.mod"), + VirtualPath::new("/workspace/a/go.mod"), + VirtualPath::new("/workspace/b/go.mod"), + VirtualPath::new("/workspace/c/go.mod"), ] ); } @@ -226,9 +228,9 @@ mod go_toolchain_tier2 { assert_eq!( output.input_files, [ - PathBuf::from("/workspace/a/go.mod"), - PathBuf::from("/workspace/b/go.mod"), - PathBuf::from("/workspace/c/go.mod"), + VirtualPath::new("/workspace/a/go.mod"), + VirtualPath::new("/workspace/b/go.mod"), + VirtualPath::new("/workspace/c/go.mod"), ] ); } @@ -392,7 +394,7 @@ mod go_toolchain_tier2 { let output = plugin .locate_dependencies_root(LocateDependenciesRootInput { - starting_dir: VirtualPath::Real(sandbox.path().into()), + starting_dir: VirtualPath::new(sandbox.path()), ..Default::default() }) .await; @@ -408,13 +410,13 @@ mod go_toolchain_tier2 { let output = plugin .locate_dependencies_root(LocateDependenciesRootInput { - starting_dir: VirtualPath::Real(sandbox.path().join("package/nested")), + starting_dir: VirtualPath::new(sandbox.path().join("package/nested")), ..Default::default() }) .await; assert!(output.members.is_none()); - assert_eq!(output.root.unwrap(), PathBuf::from("/workspace/package")); + assert_eq!(output.root.unwrap(), VirtualPath::new("/workspace/package")); } #[tokio::test(flavor = "multi_thread")] @@ -424,7 +426,7 @@ mod go_toolchain_tier2 { let output = plugin .locate_dependencies_root(LocateDependenciesRootInput { - starting_dir: VirtualPath::Real(sandbox.path().join("package-with-sum/nested")), + starting_dir: VirtualPath::new(sandbox.path().join("package-with-sum/nested")), ..Default::default() }) .await; @@ -432,7 +434,7 @@ mod go_toolchain_tier2 { assert!(output.members.is_none()); assert_eq!( output.root.unwrap(), - PathBuf::from("/workspace/package-with-sum") + VirtualPath::new("/workspace/package-with-sum") ); } @@ -443,7 +445,7 @@ mod go_toolchain_tier2 { let output = plugin .locate_dependencies_root(LocateDependenciesRootInput { - starting_dir: VirtualPath::Real( + starting_dir: VirtualPath::new( sandbox.path().join("workspace/modules/a/nested"), ), ..Default::default() @@ -451,7 +453,10 @@ mod go_toolchain_tier2 { .await; assert_eq!(output.members.unwrap(), ["modules/a"]); - assert_eq!(output.root.unwrap(), PathBuf::from("/workspace/workspace")); + assert_eq!( + output.root.unwrap(), + VirtualPath::new("/workspace/workspace") + ); } } @@ -465,7 +470,7 @@ mod go_toolchain_tier2 { let output = plugin .install_dependencies(InstallDependenciesInput { - root: VirtualPath::Real(sandbox.path().into()), + root: VirtualPath::new(sandbox.path()), toolchain_config: json!({}), ..Default::default() }) @@ -484,7 +489,7 @@ mod go_toolchain_tier2 { let output = plugin .install_dependencies(InstallDependenciesInput { - root: VirtualPath::Real(sandbox.path().into()), + root: VirtualPath::new(sandbox.path()), toolchain_config: json!({ "workspaces": true }), @@ -511,7 +516,7 @@ mod go_toolchain_tier2 { let output = plugin .install_dependencies(InstallDependenciesInput { - root: VirtualPath::Real(sandbox.path().into()), + root: VirtualPath::new(sandbox.path()), toolchain_config: json!({ "workspaces": false }), @@ -534,7 +539,7 @@ mod go_toolchain_tier2 { let output = plugin .install_dependencies(InstallDependenciesInput { - root: VirtualPath::Real(sandbox.path().into()), + root: VirtualPath::new(sandbox.path()), toolchain_config: json!({ "tidyOnChange": true, "workspaces": true @@ -562,7 +567,7 @@ mod go_toolchain_tier2 { let output = plugin .install_dependencies(InstallDependenciesInput { - root: VirtualPath::Real(sandbox.path().into()), + root: VirtualPath::new(sandbox.path()), toolchain_config: json!({}), ..Default::default() }) @@ -588,7 +593,7 @@ mod go_toolchain_tier2 { let output = plugin .install_dependencies(InstallDependenciesInput { - root: VirtualPath::Real(sandbox.path().into()), + root: VirtualPath::new(sandbox.path()), toolchain_config: json!({ "tidyOnChange": true }), @@ -622,7 +627,7 @@ mod go_toolchain_tier2 { let output = plugin .install_dependencies(InstallDependenciesInput { - root: VirtualPath::Real(sandbox.path().into()), + root: VirtualPath::new(sandbox.path()), toolchain_config: json!({ "tidyOnChange": false }), @@ -649,7 +654,7 @@ mod go_toolchain_tier2 { let output = plugin .install_dependencies(InstallDependenciesInput { - root: VirtualPath::Real(sandbox.path().into()), + root: VirtualPath::new(sandbox.path()), toolchain_config: json!({ "tidyOnChange": true }), @@ -678,7 +683,7 @@ mod go_toolchain_tier2 { let output = plugin .parse_lock(ParseLockInput { - path: VirtualPath::Real(sandbox.path().join("basic.sum")), + path: VirtualPath::new(sandbox.path().join("basic.sum")), ..Default::default() }) .await; @@ -721,7 +726,7 @@ mod go_toolchain_tier2 { let output = plugin .parse_lock(ParseLockInput { - path: VirtualPath::Real(sandbox.path().join("basic.work.sum")), + path: VirtualPath::new(sandbox.path().join("basic.work.sum")), ..Default::default() }) .await; @@ -768,7 +773,7 @@ mod go_toolchain_tier2 { let output = plugin .parse_manifest(ParseManifestInput { - path: VirtualPath::Real(sandbox.path().join("c/go.mod")), + path: VirtualPath::new(sandbox.path().join("c/go.mod")), ..Default::default() }) .await; @@ -805,7 +810,7 @@ mod go_toolchain_tier2 { let output = plugin .setup_environment(SetupEnvironmentInput { - root: VirtualPath::Real(sandbox.path().into()), + root: VirtualPath::new(sandbox.path()), toolchain_config: json!({ "bins": [] }), @@ -823,7 +828,7 @@ mod go_toolchain_tier2 { let output = plugin .setup_environment(SetupEnvironmentInput { - root: VirtualPath::Real(sandbox.path().into()), + root: VirtualPath::new(sandbox.path()), toolchain_config: json!({ "bins": [ "golang.org/x/tools/gopls", @@ -846,12 +851,14 @@ mod go_toolchain_tier2 { ) .cwd(plugin.plugin.to_virtual_path(sandbox.path())) ) - .cache("go-bins-github.com/revel/cmd@latest"), + .cache(CacheStrategy::Memory) + .label("go-bins-github.com/revel/cmd@latest"), ExecCommand::new( ExecCommandInput::new("go", ["install", "-v", "golang.org/x/tools/gopls"],) .cwd(plugin.plugin.to_virtual_path(sandbox.path())) ) - .cache("go-bins-golang.org/x/tools@latest") + .cache(CacheStrategy::Memory) + .label("go-bins-golang.org/x/tools@latest") ] ); } @@ -863,7 +870,7 @@ mod go_toolchain_tier2 { let output = plugin .setup_environment(SetupEnvironmentInput { - root: VirtualPath::Real(sandbox.path().into()), + root: VirtualPath::new(sandbox.path()), toolchain_config: json!({ "bins": [ "golang.org/x/tools/gopls@1", @@ -886,7 +893,8 @@ mod go_toolchain_tier2 { ) .cwd(plugin.plugin.to_virtual_path(sandbox.path())) ) - .cache("go-bins-github.com/revel/cmd@2"), + .cache(CacheStrategy::Memory) + .label("go-bins-github.com/revel/cmd@2"), ExecCommand::new( ExecCommandInput::new( "go", @@ -894,7 +902,8 @@ mod go_toolchain_tier2 { ) .cwd(plugin.plugin.to_virtual_path(sandbox.path())) ) - .cache("go-bins-golang.org/x/tools@1"), + .cache(CacheStrategy::Memory) + .label("go-bins-golang.org/x/tools@1"), ] ); } @@ -913,7 +922,7 @@ mod go_toolchain_tier2 { let output = plugin .setup_environment(SetupEnvironmentInput { - root: VirtualPath::Real(sandbox.path().into()), + root: VirtualPath::new(sandbox.path()), toolchain_config: json!({ "bins": [ { diff --git a/toolchains/javascript/CHANGELOG.md b/toolchains/javascript/CHANGELOG.md index 784f1897..ddcf03af 100644 --- a/toolchains/javascript/CHANGELOG.md +++ b/toolchains/javascript/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## Unreleased + +#### 🚀 Updates + +- Updated to support moon v2.5 release. + ## 1.2.1 #### 🐞 Fixes diff --git a/toolchains/javascript/src/config.rs b/toolchains/javascript/src/config.rs index 8da9ce96..6a42cf08 100644 --- a/toolchains/javascript/src/config.rs +++ b/toolchains/javascript/src/config.rs @@ -1,5 +1,5 @@ use moon_common::Id; -use moon_pdk_api::{UnresolvedVersionSpec, Version, VersionReq, config_struct}; +use moon_pdk_api::{MatchesVersion, Requirement, UnresolvedVersionSpec, Version, config_struct}; use nodejs_package_json::VersionProtocol; use rustc_hash::FxHashMap; use schematic::{Config, ConfigEnum, derive_enum}; @@ -146,23 +146,21 @@ impl SharedPackageManagerConfig { return false; }; - let req = VersionReq::parse(req).unwrap(); + let req = Requirement::parse(req).unwrap(); match spec { UnresolvedVersionSpec::Canary => true, - UnresolvedVersionSpec::Req(value) => { - let value = value.comparators.first().unwrap(); + UnresolvedVersionSpec::Requirement(value) => { let mut version = Version::new( - value.major, + value.major.unwrap_or(0), value.minor.unwrap_or(0), value.patch.unwrap_or(0), ); - version.pre = value.pre.clone(); + version.prerelease = value.prerelease.clone(); req.matches(&version) } - UnresolvedVersionSpec::Calendar(version) => req.matches(version), - UnresolvedVersionSpec::Semantic(version) => req.matches(version), + UnresolvedVersionSpec::Version(version) => req.matches(version), _ => false, } } diff --git a/toolchains/javascript/src/tier1.rs b/toolchains/javascript/src/tier1.rs index efa09db5..1f4d26ab 100644 --- a/toolchains/javascript/src/tier1.rs +++ b/toolchains/javascript/src/tier1.rs @@ -217,10 +217,8 @@ pub fn sync_project(Json(input): Json) -> FnResult) -> FnResult { paths_are_equal(dep_root, project_root.join(rel_path)) } - VersionProtocol::Requirement(req) if req == &VersionReq::STAR => { + VersionProtocol::Requirement(req) if req.comparators.is_empty() => { packages.contains_key(dep_name) } VersionProtocol::Workspace(_) => true, @@ -130,9 +130,7 @@ pub fn extend_project_graph( .extended_projects .insert(id.to_owned(), project_output); - if let Some(file) = manifest.path.virtual_path() { - output.input_files.push(file); - } + output.input_files.push(manifest.path.clone()); } Ok(Json(output)) @@ -147,8 +145,12 @@ fn gather_shared_paths( let mut current_dir = context.get_project_root(project); while current_dir != context.workspace_root { - if let Some(bin_dir) = current_dir.join("node_modules").join(".bin").real_path() { - paths.push(bin_dir); + if let Some(bin_dir) = current_dir + .join("node_modules") + .join(".bin") + .to_real_path()? + { + paths.push(bin_dir.to_path_buf()); } match current_dir.parent() { @@ -165,9 +167,9 @@ fn gather_shared_paths( .workspace_root .join("node_modules") .join(".bin") - .real_path() + .to_real_path()? { - paths.push(bin_dir); + paths.push(bin_dir.to_path_buf()); } Ok(()) @@ -349,8 +351,8 @@ pub fn locate_dependencies_root( // First attempt: find lock files if let Some(root) = locate_root_many(&input.starting_dir, &lock_names) { - output.root = root.virtual_path(); output.members = extract_workspace_members_and_catalogs(package_manager, &root)?; + output.root = Some(root); } // Second attempt: find workspace-compatible manifest files @@ -359,7 +361,7 @@ pub fn locate_dependencies_root( let mut found = false; if let Some(members) = extract_workspace_members_and_catalogs(package_manager, root)? { - output.root = root.virtual_path(); + output.root = Some(root.to_owned()); output.members = Some(members); found = true; } @@ -374,7 +376,7 @@ pub fn locate_dependencies_root( { extract_workspace_members_and_catalogs(package_manager, &root)?; - output.root = root.virtual_path(); + output.root = Some(root); } Ok(Json(output)) @@ -704,7 +706,7 @@ pub fn parse_manifest( } if let Some(version) = &manifest.version { - output.version = Some(version.to_owned()); + output.version = Some(Version::parse(version.to_string())?); } output.publishable = manifest.version.is_some() @@ -738,7 +740,7 @@ pub fn setup_environment( if package_path.exists() && let Some(version) = package_manager_config.version - && matches!(version, UnresolvedVersionSpec::Semantic(_)) + && matches!(version, UnresolvedVersionSpec::Version(_)) { let (op, file) = Operation::track("sync-package-manager", || { let mut package = PackageJson::load(package_path)?; @@ -748,7 +750,7 @@ pub fn setup_environment( output.operations.push(op); - if let Some(file) = file.and_then(|file| file.virtual_path()) { + if let Some(file) = file { output.changed_files.push(file); } } diff --git a/toolchains/javascript/tests/package_json_test.rs b/toolchains/javascript/tests/package_json_test.rs index 7d132c7f..8a498653 100644 --- a/toolchains/javascript/tests/package_json_test.rs +++ b/toolchains/javascript/tests/package_json_test.rs @@ -17,7 +17,7 @@ mod package_json { sandbox.create_file("package.json", json); let config_path = sandbox.path().join("package.json"); - let mut file = PackageJson::load(VirtualPath::Real(config_path.clone())).unwrap(); + let mut file = PackageJson::load(VirtualPath::new(config_path.clone())).unwrap(); // Trigger dirty file.dirty.push("unknown".into()); @@ -32,7 +32,7 @@ mod package_json { #[test] fn adds() { let mut tsc = PackageJson { - path: VirtualPath::Real(PathBuf::from("/base/tsconfig.json")), + path: VirtualPath::new(PathBuf::from("/base/tsconfig.json")), ..Default::default() }; @@ -52,7 +52,7 @@ mod package_json { #[test] fn overwrites() { let mut tsc = PackageJson { - path: VirtualPath::Real(PathBuf::from("/base/tsconfig.json")), + path: VirtualPath::new(PathBuf::from("/base/tsconfig.json")), data: PackageJsonInner { dependencies: Some(BTreeMap::from_iter([ ("example".into(), VersionProtocol::from_str("*").unwrap()), @@ -89,7 +89,7 @@ mod package_json { #[test] fn doesnt_overwrite() { let mut tsc = PackageJson { - path: VirtualPath::Real(PathBuf::from("/base/tsconfig.json")), + path: VirtualPath::new(PathBuf::from("/base/tsconfig.json")), data: PackageJsonInner { dependencies: Some(BTreeMap::from_iter([( "example".into(), @@ -125,7 +125,7 @@ mod package_json { #[test] fn adds() { let mut tsc = PackageJson { - path: VirtualPath::Real(PathBuf::from("/base/tsconfig.json")), + path: VirtualPath::new(PathBuf::from("/base/tsconfig.json")), ..Default::default() }; @@ -145,7 +145,7 @@ mod package_json { #[test] fn overwrites() { let mut tsc = PackageJson { - path: VirtualPath::Real(PathBuf::from("/base/tsconfig.json")), + path: VirtualPath::new(PathBuf::from("/base/tsconfig.json")), data: PackageJsonInner { dev_dependencies: Some(BTreeMap::from_iter([ ("example".into(), VersionProtocol::from_str("*").unwrap()), @@ -182,7 +182,7 @@ mod package_json { #[test] fn doesnt_overwrite() { let mut tsc = PackageJson { - path: VirtualPath::Real(PathBuf::from("/base/tsconfig.json")), + path: VirtualPath::new(PathBuf::from("/base/tsconfig.json")), data: PackageJsonInner { dev_dependencies: Some(BTreeMap::from_iter([( "example".into(), @@ -218,7 +218,7 @@ mod package_json { #[test] fn adds() { let mut tsc = PackageJson { - path: VirtualPath::Real(PathBuf::from("/base/tsconfig.json")), + path: VirtualPath::new(PathBuf::from("/base/tsconfig.json")), ..Default::default() }; @@ -238,7 +238,7 @@ mod package_json { #[test] fn overwrites() { let mut tsc = PackageJson { - path: VirtualPath::Real(PathBuf::from("/base/tsconfig.json")), + path: VirtualPath::new(PathBuf::from("/base/tsconfig.json")), data: PackageJsonInner { peer_dependencies: Some(BTreeMap::from_iter([ ("example".into(), VersionProtocol::from_str("*").unwrap()), @@ -275,7 +275,7 @@ mod package_json { #[test] fn doesnt_overwrite() { let mut tsc = PackageJson { - path: VirtualPath::Real(PathBuf::from("/base/tsconfig.json")), + path: VirtualPath::new(PathBuf::from("/base/tsconfig.json")), data: PackageJsonInner { peer_dependencies: Some(BTreeMap::from_iter([( "example".into(), @@ -311,7 +311,7 @@ mod package_json { #[test] fn sets() { let mut tsc = PackageJson { - path: VirtualPath::Real(PathBuf::from("/base/tsconfig.json")), + path: VirtualPath::new(PathBuf::from("/base/tsconfig.json")), ..Default::default() }; @@ -325,7 +325,7 @@ mod package_json { #[test] fn doesnt_set_if_empty() { let mut tsc = PackageJson { - path: VirtualPath::Real(PathBuf::from("/base/tsconfig.json")), + path: VirtualPath::new(PathBuf::from("/base/tsconfig.json")), ..Default::default() }; @@ -339,7 +339,7 @@ mod package_json { #[test] fn unsets_if_empty() { let mut tsc = PackageJson { - path: VirtualPath::Real(PathBuf::from("/base/tsconfig.json")), + path: VirtualPath::new(PathBuf::from("/base/tsconfig.json")), ..Default::default() }; @@ -353,7 +353,7 @@ mod package_json { #[test] fn doesnt_set_if_same_value() { let mut tsc = PackageJson { - path: VirtualPath::Real(PathBuf::from("/base/tsconfig.json")), + path: VirtualPath::new(PathBuf::from("/base/tsconfig.json")), ..Default::default() }; diff --git a/toolchains/javascript/tests/tier1_sync_test.rs b/toolchains/javascript/tests/tier1_sync_test.rs index 7e0ff905..00c8f9d7 100644 --- a/toolchains/javascript/tests/tier1_sync_test.rs +++ b/toolchains/javascript/tests/tier1_sync_test.rs @@ -4,7 +4,6 @@ use moon_pdk_api::*; use moon_pdk_test_utils::create_moon_sandbox; use serde_json::json; use starbase_sandbox::assert_snapshot; -use std::path::PathBuf; mod javascript_toolchain_tier1 { use super::*; @@ -189,7 +188,7 @@ mod javascript_toolchain_tier1 { assert!(!output.operations.is_empty()); assert_eq!( output.changed_files, - vec![PathBuf::from("/workspace/base/package.json")] + vec![VirtualPath::new("/workspace/base/package.json")] ); assert_snapshot!( format!("format_{format}"), diff --git a/toolchains/javascript/tests/tier1_test.rs b/toolchains/javascript/tests/tier1_test.rs index 814e812a..b18e7e4d 100644 --- a/toolchains/javascript/tests/tier1_test.rs +++ b/toolchains/javascript/tests/tier1_test.rs @@ -2,7 +2,6 @@ use moon_config::DockerPruneConfig; use moon_pdk_api::*; use moon_pdk_test_utils::create_empty_moon_sandbox; use starbase_utils::json::JsonValue; -use std::path::PathBuf; mod javascript_toolchain_tier1 { use super::*; @@ -198,7 +197,7 @@ mod javascript_toolchain_tier1 { delete_vendor_directories: true, ..Default::default() }, - root: VirtualPath::Real(sandbox.path().into()), + root: VirtualPath::new(sandbox.path()), ..Default::default() }) .await; @@ -219,7 +218,7 @@ mod javascript_toolchain_tier1 { delete_vendor_directories: false, ..Default::default() }, - root: VirtualPath::Real(sandbox.path().into()), + root: VirtualPath::new(sandbox.path()), ..Default::default() }) .await; @@ -240,7 +239,7 @@ mod javascript_toolchain_tier1 { delete_vendor_directories: true, ..Default::default() }, - root: VirtualPath::Real(sandbox.path().into()), + root: VirtualPath::new(sandbox.path()), ..Default::default() }) .await; @@ -249,7 +248,7 @@ mod javascript_toolchain_tier1 { assert_eq!( output.changed_files, - [PathBuf::from("/workspace/node_modules")] + [VirtualPath::new("/workspace/node_modules")] ); } } diff --git a/toolchains/javascript/tests/tier2_env_test.rs b/toolchains/javascript/tests/tier2_env_test.rs index 00fd85aa..e654a360 100644 --- a/toolchains/javascript/tests/tier2_env_test.rs +++ b/toolchains/javascript/tests/tier2_env_test.rs @@ -2,7 +2,6 @@ use moon_pdk_api::*; use moon_pdk_test_utils::create_moon_sandbox; use serde_json::json; use std::fs; -use std::path::PathBuf; mod javascript_toolchain_tier2 { use super::*; @@ -22,7 +21,7 @@ mod javascript_toolchain_tier2 { let output = plugin .setup_environment(SetupEnvironmentInput { - root: VirtualPath::Real(sandbox.path().into()), + root: VirtualPath::new(sandbox.path()), toolchain_config: json!({ "syncPackageManagerField": false, "packageManager": "npm" @@ -52,7 +51,7 @@ mod javascript_toolchain_tier2 { let output = plugin .setup_environment(SetupEnvironmentInput { - root: VirtualPath::Real(sandbox.path().into()), + root: VirtualPath::new(sandbox.path()), toolchain_config: json!({ "syncPackageManagerField": true, "packageManager": null @@ -82,7 +81,7 @@ mod javascript_toolchain_tier2 { let output = plugin .setup_environment(SetupEnvironmentInput { - root: VirtualPath::Real(sandbox.path().into()), + root: VirtualPath::new(sandbox.path()), toolchain_config: json!({ "syncPackageManagerField": true, "packageManager": "npm" @@ -112,7 +111,7 @@ mod javascript_toolchain_tier2 { let output = plugin .setup_environment(SetupEnvironmentInput { - root: VirtualPath::Real(sandbox.path().into()), + root: VirtualPath::new(sandbox.path()), toolchain_config: json!({ "syncPackageManagerField": true, "packageManager": "npm" @@ -142,7 +141,7 @@ mod javascript_toolchain_tier2 { let output = plugin .setup_environment(SetupEnvironmentInput { - root: VirtualPath::Real(sandbox.path().into()), + root: VirtualPath::new(sandbox.path()), toolchain_config: json!({ "syncPackageManagerField": true, "packageManager": "bun" @@ -172,7 +171,7 @@ mod javascript_toolchain_tier2 { let output = plugin .setup_environment(SetupEnvironmentInput { - root: VirtualPath::Real(sandbox.path().into()), + root: VirtualPath::new(sandbox.path()), toolchain_config: json!({ "syncPackageManagerField": true, "packageManager": "deno" @@ -202,7 +201,7 @@ mod javascript_toolchain_tier2 { let output = plugin .setup_environment(SetupEnvironmentInput { - root: VirtualPath::Real(sandbox.path().into()), + root: VirtualPath::new(sandbox.path()), toolchain_config: json!({ "syncPackageManagerField": true, "packageManager": "npm" @@ -213,7 +212,7 @@ mod javascript_toolchain_tier2 { assert_eq!( output.changed_files, - [PathBuf::from("/workspace/package.json")] + [VirtualPath::new("/workspace/package.json")] ); assert!( fs::read_to_string(sandbox.path().join("package.json")) diff --git a/toolchains/javascript/tests/tier2_test.rs b/toolchains/javascript/tests/tier2_test.rs index e1a08cf7..5bcac4ef 100644 --- a/toolchains/javascript/tests/tier2_test.rs +++ b/toolchains/javascript/tests/tier2_test.rs @@ -9,7 +9,6 @@ use moon_target::Target; use serde_json::json; use starbase_utils::fs; use std::collections::BTreeMap; -use std::path::PathBuf; mod javascript_toolchain_tier2 { use super::*; @@ -68,14 +67,14 @@ mod javascript_toolchain_tier2 { ]) ); - output.input_files.sort(); + output.input_files.sort_by(|a, d| a.cmp(d)); assert_eq!( output.input_files, [ - PathBuf::from("/workspace/a/package.json"), - PathBuf::from("/workspace/b/package.json"), - PathBuf::from("/workspace/c/package.json"), + VirtualPath::new("/workspace/a/package.json"), + VirtualPath::new("/workspace/b/package.json"), + VirtualPath::new("/workspace/c/package.json"), ] ); } @@ -103,7 +102,7 @@ mod javascript_toolchain_tier2 { assert_eq!( output.input_files, - [PathBuf::from("/workspace/a/package.json")] + [VirtualPath::new("/workspace/a/package.json")] ); } @@ -457,7 +456,7 @@ mod javascript_toolchain_tier2 { let output = plugin .locate_dependencies_root(LocateDependenciesRootInput { - starting_dir: VirtualPath::Real(sandbox.path().into()), + starting_dir: VirtualPath::new(sandbox.path()), toolchain_config: json!({ "packageManager": "npm" }), @@ -476,7 +475,7 @@ mod javascript_toolchain_tier2 { let output = plugin .locate_dependencies_root(LocateDependenciesRootInput { - starting_dir: VirtualPath::Real(sandbox.path().join("package")), + starting_dir: VirtualPath::new(sandbox.path().join("package")), toolchain_config: json!({ "packageManager": null }), @@ -495,7 +494,7 @@ mod javascript_toolchain_tier2 { let output = plugin .locate_dependencies_root(LocateDependenciesRootInput { - starting_dir: VirtualPath::Real(sandbox.path().join("package/nested")), + starting_dir: VirtualPath::new(sandbox.path().join("package/nested")), toolchain_config: json!({ "packageManager": "npm" }), @@ -504,7 +503,7 @@ mod javascript_toolchain_tier2 { .await; assert!(output.members.is_none()); - assert_eq!(output.root.unwrap(), PathBuf::from("/workspace/package")); + assert_eq!(output.root.unwrap(), VirtualPath::new("/workspace/package")); } #[tokio::test(flavor = "multi_thread")] @@ -516,7 +515,7 @@ mod javascript_toolchain_tier2 { let output = plugin .locate_dependencies_root(LocateDependenciesRootInput { - starting_dir: VirtualPath::Real(sandbox.path().join("package/nested")), + starting_dir: VirtualPath::new(sandbox.path().join("package/nested")), toolchain_config: json!({ "packageManager": "bun" }), @@ -525,7 +524,7 @@ mod javascript_toolchain_tier2 { .await; assert!(output.members.is_none()); - assert_eq!(output.root.unwrap(), PathBuf::from("/workspace/package")); + assert_eq!(output.root.unwrap(), VirtualPath::new("/workspace/package")); } #[tokio::test(flavor = "multi_thread")] @@ -537,7 +536,7 @@ mod javascript_toolchain_tier2 { let output = plugin .locate_dependencies_root(LocateDependenciesRootInput { - starting_dir: VirtualPath::Real(sandbox.path().join("package/nested")), + starting_dir: VirtualPath::new(sandbox.path().join("package/nested")), toolchain_config: json!({ "packageManager": "deno" }), @@ -546,7 +545,7 @@ mod javascript_toolchain_tier2 { .await; assert!(output.members.is_none()); - assert_eq!(output.root.unwrap(), PathBuf::from("/workspace/package")); + assert_eq!(output.root.unwrap(), VirtualPath::new("/workspace/package")); } #[tokio::test(flavor = "multi_thread")] @@ -564,7 +563,7 @@ mod javascript_toolchain_tier2 { let output = plugin .locate_dependencies_root(LocateDependenciesRootInput { - starting_dir: VirtualPath::Real(sandbox.path().join("package/nested/app")), + starting_dir: VirtualPath::new(sandbox.path().join("package/nested/app")), toolchain_config: json!({ "packageManager": "deno" }), @@ -573,7 +572,7 @@ mod javascript_toolchain_tier2 { .await; assert!(output.members.is_none()); - assert_eq!(output.root.unwrap(), PathBuf::from("/workspace/package")); + assert_eq!(output.root.unwrap(), VirtualPath::new("/workspace/package")); } #[tokio::test(flavor = "multi_thread")] @@ -585,7 +584,7 @@ mod javascript_toolchain_tier2 { let output = plugin .locate_dependencies_root(LocateDependenciesRootInput { - starting_dir: VirtualPath::Real(sandbox.path().join("package/nested")), + starting_dir: VirtualPath::new(sandbox.path().join("package/nested")), toolchain_config: json!({ "packageManager": "npm" }), @@ -594,7 +593,7 @@ mod javascript_toolchain_tier2 { .await; assert!(output.members.is_none()); - assert_eq!(output.root.unwrap(), PathBuf::from("/workspace/package")); + assert_eq!(output.root.unwrap(), VirtualPath::new("/workspace/package")); } #[tokio::test(flavor = "multi_thread")] @@ -606,7 +605,7 @@ mod javascript_toolchain_tier2 { let output = plugin .locate_dependencies_root(LocateDependenciesRootInput { - starting_dir: VirtualPath::Real(sandbox.path().join("package/nested")), + starting_dir: VirtualPath::new(sandbox.path().join("package/nested")), toolchain_config: json!({ "packageManager": "pnpm" }), @@ -615,7 +614,7 @@ mod javascript_toolchain_tier2 { .await; assert!(output.members.is_none()); - assert_eq!(output.root.unwrap(), PathBuf::from("/workspace/package")); + assert_eq!(output.root.unwrap(), VirtualPath::new("/workspace/package")); } #[tokio::test(flavor = "multi_thread")] @@ -627,7 +626,7 @@ mod javascript_toolchain_tier2 { let output = plugin .locate_dependencies_root(LocateDependenciesRootInput { - starting_dir: VirtualPath::Real(sandbox.path().join("package/nested")), + starting_dir: VirtualPath::new(sandbox.path().join("package/nested")), toolchain_config: json!({ "packageManager": "yarn" }), @@ -636,7 +635,7 @@ mod javascript_toolchain_tier2 { .await; assert!(output.members.is_none()); - assert_eq!(output.root.unwrap(), PathBuf::from("/workspace/package")); + assert_eq!(output.root.unwrap(), VirtualPath::new("/workspace/package")); } #[tokio::test(flavor = "multi_thread")] @@ -646,7 +645,7 @@ mod javascript_toolchain_tier2 { let output = plugin .locate_dependencies_root(LocateDependenciesRootInput { - starting_dir: VirtualPath::Real( + starting_dir: VirtualPath::new( sandbox.path().join("workspace/packages/a/nested"), ), toolchain_config: json!({ @@ -657,7 +656,10 @@ mod javascript_toolchain_tier2 { .await; assert_eq!(output.members.unwrap(), ["packages/*"]); - assert_eq!(output.root.unwrap(), PathBuf::from("/workspace/workspace")); + assert_eq!( + output.root.unwrap(), + VirtualPath::new("/workspace/workspace") + ); } #[tokio::test(flavor = "multi_thread")] @@ -669,7 +671,7 @@ mod javascript_toolchain_tier2 { let output = plugin .locate_dependencies_root(LocateDependenciesRootInput { - starting_dir: VirtualPath::Real( + starting_dir: VirtualPath::new( sandbox.path().join("workspace/packages/a/nested"), ), toolchain_config: json!({ @@ -680,7 +682,10 @@ mod javascript_toolchain_tier2 { .await; assert_eq!(output.members.unwrap(), ["packages/*"]); - assert_eq!(output.root.unwrap(), PathBuf::from("/workspace/workspace")); + assert_eq!( + output.root.unwrap(), + VirtualPath::new("/workspace/workspace") + ); } #[tokio::test(flavor = "multi_thread")] @@ -692,7 +697,7 @@ mod javascript_toolchain_tier2 { let output = plugin .locate_dependencies_root(LocateDependenciesRootInput { - starting_dir: VirtualPath::Real( + starting_dir: VirtualPath::new( sandbox.path().join("workspace/packages/a/nested"), ), toolchain_config: json!({ @@ -703,7 +708,10 @@ mod javascript_toolchain_tier2 { .await; assert_eq!(output.members.unwrap(), ["packages/*"]); - assert_eq!(output.root.unwrap(), PathBuf::from("/workspace/workspace")); + assert_eq!( + output.root.unwrap(), + VirtualPath::new("/workspace/workspace") + ); } #[tokio::test(flavor = "multi_thread")] @@ -723,7 +731,7 @@ mod javascript_toolchain_tier2 { let output = plugin .locate_dependencies_root(LocateDependenciesRootInput { - starting_dir: VirtualPath::Real( + starting_dir: VirtualPath::new( sandbox.path().join("workspace/packages/a/nested"), ), toolchain_config: json!({ @@ -734,7 +742,10 @@ mod javascript_toolchain_tier2 { .await; assert_eq!(output.members.unwrap(), ["packages/*"]); - assert_eq!(output.root.unwrap(), PathBuf::from("/workspace/workspace")); + assert_eq!( + output.root.unwrap(), + VirtualPath::new("/workspace/workspace") + ); } #[tokio::test(flavor = "multi_thread")] @@ -746,7 +757,7 @@ mod javascript_toolchain_tier2 { let output = plugin .locate_dependencies_root(LocateDependenciesRootInput { - starting_dir: VirtualPath::Real( + starting_dir: VirtualPath::new( sandbox.path().join("workspace/packages/a/nested"), ), toolchain_config: json!({ @@ -757,7 +768,10 @@ mod javascript_toolchain_tier2 { .await; assert_eq!(output.members.unwrap(), ["packages/*"]); - assert_eq!(output.root.unwrap(), PathBuf::from("/workspace/workspace")); + assert_eq!( + output.root.unwrap(), + VirtualPath::new("/workspace/workspace") + ); } #[tokio::test(flavor = "multi_thread")] @@ -769,7 +783,7 @@ mod javascript_toolchain_tier2 { let output = plugin .locate_dependencies_root(LocateDependenciesRootInput { - starting_dir: VirtualPath::Real( + starting_dir: VirtualPath::new( sandbox.path().join("workspace/packages/a/nested"), ), toolchain_config: json!({ @@ -780,7 +794,10 @@ mod javascript_toolchain_tier2 { .await; assert_eq!(output.members.unwrap(), ["packages/*"]); - assert_eq!(output.root.unwrap(), PathBuf::from("/workspace/workspace")); + assert_eq!( + output.root.unwrap(), + VirtualPath::new("/workspace/workspace") + ); } #[tokio::test(flavor = "multi_thread")] @@ -792,7 +809,7 @@ mod javascript_toolchain_tier2 { let output = plugin .locate_dependencies_root(LocateDependenciesRootInput { - starting_dir: VirtualPath::Real( + starting_dir: VirtualPath::new( sandbox.path().join("workspace/packages/a/nested"), ), toolchain_config: json!({ @@ -803,7 +820,10 @@ mod javascript_toolchain_tier2 { .await; assert_eq!(output.members.unwrap(), ["apps/*"]); - assert_eq!(output.root.unwrap(), PathBuf::from("/workspace/workspace")); + assert_eq!( + output.root.unwrap(), + VirtualPath::new("/workspace/workspace") + ); } #[tokio::test(flavor = "multi_thread")] @@ -815,7 +835,7 @@ mod javascript_toolchain_tier2 { let output = plugin .locate_dependencies_root(LocateDependenciesRootInput { - starting_dir: VirtualPath::Real( + starting_dir: VirtualPath::new( sandbox.path().join("workspace/packages/a/nested"), ), toolchain_config: json!({ @@ -826,7 +846,10 @@ mod javascript_toolchain_tier2 { .await; assert_eq!(output.members.unwrap(), ["packages/*"]); - assert_eq!(output.root.unwrap(), PathBuf::from("/workspace/workspace")); + assert_eq!( + output.root.unwrap(), + VirtualPath::new("/workspace/workspace") + ); } #[tokio::test(flavor = "multi_thread")] @@ -838,7 +861,7 @@ mod javascript_toolchain_tier2 { let output = plugin .locate_dependencies_root(LocateDependenciesRootInput { - starting_dir: VirtualPath::Real( + starting_dir: VirtualPath::new( sandbox.path().join("workspace/packages/a/nested"), ), toolchain_config: json!({ @@ -849,7 +872,10 @@ mod javascript_toolchain_tier2 { .await; assert_eq!(output.members.unwrap(), ["packages/*"]); - assert_eq!(output.root.unwrap(), PathBuf::from("/workspace/workspace")); + assert_eq!( + output.root.unwrap(), + VirtualPath::new("/workspace/workspace") + ); } } @@ -863,7 +889,7 @@ mod javascript_toolchain_tier2 { let output = plugin .install_dependencies(InstallDependenciesInput { - root: VirtualPath::Real(sandbox.path().into()), + root: VirtualPath::new(sandbox.path()), toolchain_config: json!({ "packageManager": null }), @@ -885,7 +911,7 @@ mod javascript_toolchain_tier2 { let output = plugin .install_dependencies(InstallDependenciesInput { - root: VirtualPath::Real(sandbox.path().into()), + root: VirtualPath::new(sandbox.path()), toolchain_config: json!({ "packageManager": "bun", "dedupeOnLockfileChange": true @@ -911,7 +937,7 @@ mod javascript_toolchain_tier2 { let output = plugin .install_dependencies(InstallDependenciesInput { - root: VirtualPath::Real(sandbox.path().into()), + root: VirtualPath::new(sandbox.path()), toolchain_config: json!({ "packageManager": "bun", "dedupeOnLockfileChange": true @@ -954,7 +980,7 @@ mod javascript_toolchain_tier2 { let output = plugin .install_dependencies(InstallDependenciesInput { - root: VirtualPath::Real(sandbox.path().into()), + root: VirtualPath::new(sandbox.path()), toolchain_config: json!({ "packageManager": "bun", "dedupeOnLockfileChange": true @@ -984,7 +1010,7 @@ mod javascript_toolchain_tier2 { let output = plugin .install_dependencies(InstallDependenciesInput { - root: VirtualPath::Real(sandbox.path().into()), + root: VirtualPath::new(sandbox.path()), toolchain_config: json!({ "packageManager": "deno", "dedupeOnLockfileChange": true @@ -1015,7 +1041,7 @@ mod javascript_toolchain_tier2 { let output = plugin .install_dependencies(InstallDependenciesInput { - root: VirtualPath::Real(sandbox.path().into()), + root: VirtualPath::new(sandbox.path()), toolchain_config: json!({ "packageManager": "deno", "dedupeOnLockfileChange": true @@ -1049,7 +1075,7 @@ mod javascript_toolchain_tier2 { let output = plugin .install_dependencies(InstallDependenciesInput { - root: VirtualPath::Real(sandbox.path().into()), + root: VirtualPath::new(sandbox.path()), toolchain_config: json!({ "packageManager": "deno", }), @@ -1087,7 +1113,7 @@ mod javascript_toolchain_tier2 { // No lockfile — stays as `deno install` let output = plugin .install_dependencies(InstallDependenciesInput { - root: VirtualPath::Real(sandbox.path().into()), + root: VirtualPath::new(sandbox.path()), toolchain_config: json!({ "packageManager": "deno", }), @@ -1107,7 +1133,7 @@ mod javascript_toolchain_tier2 { let output = plugin .install_dependencies(InstallDependenciesInput { - root: VirtualPath::Real(sandbox.path().into()), + root: VirtualPath::new(sandbox.path()), toolchain_config: json!({ "packageManager": "deno", }), @@ -1145,7 +1171,7 @@ mod javascript_toolchain_tier2 { let output = plugin .install_dependencies(InstallDependenciesInput { - root: VirtualPath::Real(sandbox.path().into()), + root: VirtualPath::new(sandbox.path()), toolchain_config: json!({ "packageManager": "deno", }), @@ -1174,7 +1200,7 @@ mod javascript_toolchain_tier2 { let output = plugin .install_dependencies(InstallDependenciesInput { - root: VirtualPath::Real(sandbox.path().into()), + root: VirtualPath::new(sandbox.path()), toolchain_config: json!({ "packageManager": "deno", "dedupeOnLockfileChange": true @@ -1204,7 +1230,7 @@ mod javascript_toolchain_tier2 { let output = plugin .install_dependencies(InstallDependenciesInput { - root: VirtualPath::Real(sandbox.path().into()), + root: VirtualPath::new(sandbox.path()), toolchain_config: json!({ "packageManager": "npm", "dedupeOnLockfileChange": true @@ -1236,7 +1262,7 @@ mod javascript_toolchain_tier2 { let output = plugin .install_dependencies(InstallDependenciesInput { - root: VirtualPath::Real(sandbox.path().into()), + root: VirtualPath::new(sandbox.path()), toolchain_config: json!({ "packageManager": "npm", "dedupeOnLockfileChange": true @@ -1285,7 +1311,7 @@ mod javascript_toolchain_tier2 { let output = plugin .install_dependencies(InstallDependenciesInput { - root: VirtualPath::Real(sandbox.path().into()), + root: VirtualPath::new(sandbox.path()), toolchain_config: json!({ "packageManager": "npm", "dedupeOnLockfileChange": true @@ -1325,7 +1351,7 @@ mod javascript_toolchain_tier2 { // Doesn't work without the lockfile let output = plugin .install_dependencies(InstallDependenciesInput { - root: VirtualPath::Real(sandbox.path().into()), + root: VirtualPath::new(sandbox.path()), toolchain_config: json!({ "packageManager": "npm", }), @@ -1346,7 +1372,7 @@ mod javascript_toolchain_tier2 { let output = plugin .install_dependencies(InstallDependenciesInput { - root: VirtualPath::Real(sandbox.path().into()), + root: VirtualPath::new(sandbox.path()), toolchain_config: json!({ "packageManager": "npm", }), @@ -1374,7 +1400,7 @@ mod javascript_toolchain_tier2 { let output = plugin .install_dependencies(InstallDependenciesInput { - root: VirtualPath::Real(sandbox.path().into()), + root: VirtualPath::new(sandbox.path()), toolchain_config: json!({ "packageManager": "pnpm", "dedupeOnLockfileChange": true @@ -1399,7 +1425,7 @@ mod javascript_toolchain_tier2 { let output = plugin .install_dependencies(InstallDependenciesInput { - root: VirtualPath::Real(sandbox.path().into()), + root: VirtualPath::new(sandbox.path()), toolchain_config: json!({ "packageManager": "pnpm", "dedupeOnLockfileChange": true @@ -1441,7 +1467,7 @@ mod javascript_toolchain_tier2 { let output = plugin .install_dependencies(InstallDependenciesInput { - root: VirtualPath::Real(sandbox.path().into()), + root: VirtualPath::new(sandbox.path()), toolchain_config: json!({ "packageManager": "pnpm", "dedupeOnLockfileChange": true @@ -1471,7 +1497,7 @@ mod javascript_toolchain_tier2 { let output = plugin .install_dependencies(InstallDependenciesInput { - root: VirtualPath::Real(sandbox.path().into()), + root: VirtualPath::new(sandbox.path()), toolchain_config: json!({ "packageManager": "pnpm", "dedupeOnLockfileChange": true @@ -1510,7 +1536,7 @@ mod javascript_toolchain_tier2 { let output = plugin .install_dependencies(InstallDependenciesInput { - root: VirtualPath::Real(sandbox.path().into()), + root: VirtualPath::new(sandbox.path()), toolchain_config: json!({ "packageManager": "pnpm", "dedupeOnLockfileChange": true, @@ -1539,7 +1565,7 @@ mod javascript_toolchain_tier2 { let output = plugin .install_dependencies(InstallDependenciesInput { - root: VirtualPath::Real(sandbox.path().into()), + root: VirtualPath::new(sandbox.path()), toolchain_config: json!({ "packageManager": "yarn", "dedupeOnLockfileChange": true @@ -1564,7 +1590,7 @@ mod javascript_toolchain_tier2 { let output = plugin .install_dependencies(InstallDependenciesInput { - root: VirtualPath::Real(sandbox.path().into()), + root: VirtualPath::new(sandbox.path()), toolchain_config: json!({ "packageManager": "yarn", "dedupeOnLockfileChange": true @@ -1596,7 +1622,7 @@ mod javascript_toolchain_tier2 { let output = plugin .install_dependencies(InstallDependenciesInput { - root: VirtualPath::Real(sandbox.path().into()), + root: VirtualPath::new(sandbox.path()), toolchain_config: json!({ "packageManager": "yarn", "dedupeOnLockfileChange": true @@ -1631,7 +1657,7 @@ mod javascript_toolchain_tier2 { let output = plugin .install_dependencies(InstallDependenciesInput { - root: VirtualPath::Real(sandbox.path().into()), + root: VirtualPath::new(sandbox.path()), toolchain_config: json!({ "packageManager": "yarn", "dedupeOnLockfileChange": true @@ -1661,7 +1687,7 @@ mod javascript_toolchain_tier2 { let output = plugin .install_dependencies(InstallDependenciesInput { - root: VirtualPath::Real(sandbox.path().into()), + root: VirtualPath::new(sandbox.path()), toolchain_config: json!({ "packageManager": "pnpm", "dedupeOnLockfileChange": true @@ -1700,7 +1726,7 @@ mod javascript_toolchain_tier2 { let output = plugin .install_dependencies(InstallDependenciesInput { - root: VirtualPath::Real(sandbox.path().into()), + root: VirtualPath::new(sandbox.path()), toolchain_config: json!({ "packageManager": "yarn", "dedupeOnLockfileChange": true, @@ -1730,7 +1756,7 @@ mod javascript_toolchain_tier2 { let output = plugin .parse_manifest(ParseManifestInput { - path: VirtualPath::Real(sandbox.path().join("package.json")), + path: VirtualPath::new(sandbox.path().join("package.json")), ..Default::default() }) .await; @@ -1771,7 +1797,7 @@ mod javascript_toolchain_tier2 { let output = plugin .parse_manifest(ParseManifestInput { - path: VirtualPath::Real(sandbox.path().join("package/package.json")), + path: VirtualPath::new(sandbox.path().join("package/package.json")), ..Default::default() }) .await; @@ -1833,7 +1859,7 @@ mod javascript_toolchain_tier2 { // This must be ran to extract the catalogs plugin .locate_dependencies_root(LocateDependenciesRootInput { - starting_dir: VirtualPath::Real(sandbox.path().join("package")), + starting_dir: VirtualPath::new(sandbox.path().join("package")), toolchain_config: json!({ "packageManager": "npm" }), @@ -1843,8 +1869,8 @@ mod javascript_toolchain_tier2 { let output = plugin .parse_manifest(ParseManifestInput { - path: VirtualPath::Real(sandbox.path().join("package/package.json")), - root: VirtualPath::Real(sandbox.path().into()), + path: VirtualPath::new(sandbox.path().join("package/package.json")), + root: VirtualPath::new(sandbox.path()), ..Default::default() }) .await; @@ -1874,7 +1900,7 @@ mod javascript_toolchain_tier2 { // This must be ran to extract the catalogs plugin .locate_dependencies_root(LocateDependenciesRootInput { - starting_dir: VirtualPath::Real(sandbox.path().join("package")), + starting_dir: VirtualPath::new(sandbox.path().join("package")), toolchain_config: json!({ "packageManager": "pnpm" }), @@ -1884,8 +1910,8 @@ mod javascript_toolchain_tier2 { let output = plugin .parse_manifest(ParseManifestInput { - path: VirtualPath::Real(sandbox.path().join("package/package.json")), - root: VirtualPath::Real(sandbox.path().into()), + path: VirtualPath::new(sandbox.path().join("package/package.json")), + root: VirtualPath::new(sandbox.path()), ..Default::default() }) .await; @@ -1915,7 +1941,7 @@ mod javascript_toolchain_tier2 { // This must be ran to extract the catalogs plugin .locate_dependencies_root(LocateDependenciesRootInput { - starting_dir: VirtualPath::Real(sandbox.path().join("package")), + starting_dir: VirtualPath::new(sandbox.path().join("package")), toolchain_config: json!({ "packageManager": "yarn" }), @@ -1925,8 +1951,8 @@ mod javascript_toolchain_tier2 { let output = plugin .parse_manifest(ParseManifestInput { - path: VirtualPath::Real(sandbox.path().join("package/package.json")), - root: VirtualPath::Real(sandbox.path().into()), + path: VirtualPath::new(sandbox.path().join("package/package.json")), + root: VirtualPath::new(sandbox.path()), ..Default::default() }) .await; @@ -1956,7 +1982,7 @@ mod javascript_toolchain_tier2 { // This must be ran to extract the catalogs plugin .locate_dependencies_root(LocateDependenciesRootInput { - starting_dir: VirtualPath::Real(sandbox.path().join("package")), + starting_dir: VirtualPath::new(sandbox.path().join("package")), toolchain_config: json!({ "packageManager": "deno" }), @@ -1966,8 +1992,8 @@ mod javascript_toolchain_tier2 { let output = plugin .parse_manifest(ParseManifestInput { - path: VirtualPath::Real(sandbox.path().join("package/package.json")), - root: VirtualPath::Real(sandbox.path().into()), + path: VirtualPath::new(sandbox.path().join("package/package.json")), + root: VirtualPath::new(sandbox.path()), ..Default::default() }) .await; @@ -2108,7 +2134,7 @@ mod javascript_toolchain_tier2 { let output = plugin .parse_lock(ParseLockInput { - path: VirtualPath::Real(sandbox.path().join("bun.lock")), + path: VirtualPath::new(sandbox.path().join("bun.lock")), ..Default::default() }) .await; @@ -2123,7 +2149,7 @@ mod javascript_toolchain_tier2 { // let output = plugin // .parse_lock(ParseLockInput { - // path: VirtualPath::Real(sandbox.path().join("bun.lockb")), + // path: VirtualPath::new(sandbox.path().join("bun.lockb")), // ..Default::default() // }) // .await; @@ -2152,7 +2178,7 @@ mod javascript_toolchain_tier2 { let output = plugin .parse_lock(ParseLockInput { - path: VirtualPath::Real(sandbox.path().join("deno.lock")), + path: VirtualPath::new(sandbox.path().join("deno.lock")), ..Default::default() }) .await; @@ -2215,7 +2241,7 @@ mod javascript_toolchain_tier2 { let output = plugin .parse_lock(ParseLockInput { - path: VirtualPath::Real(sandbox.path().join("package-lock.json")), + path: VirtualPath::new(sandbox.path().join("package-lock.json")), ..Default::default() }) .await; @@ -2230,7 +2256,7 @@ mod javascript_toolchain_tier2 { let output = plugin .parse_lock(ParseLockInput { - path: VirtualPath::Real(sandbox.path().join("pnpm-lock.yaml")), + path: VirtualPath::new(sandbox.path().join("pnpm-lock.yaml")), ..Default::default() }) .await; @@ -2248,7 +2274,7 @@ mod javascript_toolchain_tier2 { let output = plugin .parse_lock(ParseLockInput { - path: VirtualPath::Real(sandbox.path().join("pnpm-lock.yaml")), + path: VirtualPath::new(sandbox.path().join("pnpm-lock.yaml")), ..Default::default() }) .await; @@ -2286,7 +2312,7 @@ mod javascript_toolchain_tier2 { let output = plugin .parse_lock(ParseLockInput { - path: VirtualPath::Real(sandbox.path().join("yarn.lock")), + path: VirtualPath::new(sandbox.path().join("yarn.lock")), ..Default::default() }) .await; @@ -2372,7 +2398,7 @@ mod javascript_toolchain_tier2 { let output = plugin .parse_lock(ParseLockInput { - path: VirtualPath::Real(sandbox.path().join("yarn.lock")), + path: VirtualPath::new(sandbox.path().join("yarn.lock")), ..Default::default() }) .await; diff --git a/toolchains/node-depman/CHANGELOG.md b/toolchains/node-depman/CHANGELOG.md index 27c64525..6ac73c62 100644 --- a/toolchains/node-depman/CHANGELOG.md +++ b/toolchains/node-depman/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## Unreleased + +#### 🚀 Updates + +- Updated to support moon v2.5 release. + ## 1.0.3 #### 🚀 Updates diff --git a/toolchains/node-depman/tests/tier2_test.rs b/toolchains/node-depman/tests/tier2_test.rs index 5a9d0f16..9e2dd39a 100644 --- a/toolchains/node-depman/tests/tier2_test.rs +++ b/toolchains/node-depman/tests/tier2_test.rs @@ -15,7 +15,7 @@ mod node_depman_toolchain_tier2 { let output = plugin .setup_environment(SetupEnvironmentInput { - root: VirtualPath::Real(sandbox.path().into()), + root: VirtualPath::new(sandbox.path()), toolchain_config: json!({}), ..Default::default() }) @@ -31,7 +31,7 @@ mod node_depman_toolchain_tier2 { let output = plugin .setup_environment(SetupEnvironmentInput { - root: VirtualPath::Real(sandbox.path().into()), + root: VirtualPath::new(sandbox.path()), toolchain_config: json!({}), ..Default::default() }) @@ -47,7 +47,7 @@ mod node_depman_toolchain_tier2 { let output = plugin .setup_environment(SetupEnvironmentInput { - root: VirtualPath::Real(sandbox.path().into()), + root: VirtualPath::new(sandbox.path()), toolchain_config: json!({ "plugins": [], "version": "^2" @@ -60,7 +60,7 @@ mod node_depman_toolchain_tier2 { let output = plugin .setup_environment(SetupEnvironmentInput { - root: VirtualPath::Real(sandbox.path().into()), + root: VirtualPath::new(sandbox.path()), toolchain_config: json!({ "version": "^2" }), @@ -78,7 +78,7 @@ mod node_depman_toolchain_tier2 { let output = plugin .setup_environment(SetupEnvironmentInput { - root: VirtualPath::Real(sandbox.path().into()), + root: VirtualPath::new(sandbox.path()), toolchain_config: json!({ "plugins": ["example"], "version": "^1" @@ -97,7 +97,7 @@ mod node_depman_toolchain_tier2 { let output = plugin .setup_environment(SetupEnvironmentInput { - root: VirtualPath::Real(sandbox.path().into()), + root: VirtualPath::new(sandbox.path()), toolchain_config: json!({ "plugins": ["foo", "bar"], "version": "^2" diff --git a/toolchains/node/CHANGELOG.md b/toolchains/node/CHANGELOG.md index cd737571..cef8fb35 100644 --- a/toolchains/node/CHANGELOG.md +++ b/toolchains/node/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## Unreleased + +#### 🚀 Updates + +- Updated to support moon v2.5 release. + ## 1.0.2 #### 🚀 Updates diff --git a/toolchains/node/src/tier2.rs b/toolchains/node/src/tier2.rs index f69ff3a2..19366bff 100644 --- a/toolchains/node/src/tier2.rs +++ b/toolchains/node/src/tier2.rs @@ -2,7 +2,7 @@ use crate::config::*; use extism_pdk::*; -use moon_pdk::parse_toolchain_config; +use moon_pdk::{VirtualPathExt, parse_toolchain_config}; use moon_pdk_api::*; use starbase_utils::fs; @@ -29,7 +29,7 @@ pub fn setup_environment( })?; output.operations.push(op); - output.changed_files.extend(file.virtual_path()); + output.changed_files.push(file); } Ok(Json(output)) @@ -47,7 +47,7 @@ pub fn extend_task_command( let project_root = input.context.get_project_root(&input.project); if let Some(profile) = &config.profile_execution - && let Some(prof_dir) = project_root.join(".moon").real_path_string() + && let Some(prof_dir) = project_root.join(".moon").to_real_path()? { match profile { NodeProfileType::Cpu => { @@ -56,7 +56,7 @@ pub fn extend_task_command( "--cpu-prof-name".into(), "snapshot.cpuprofile".into(), "--cpu-prof-dir".into(), - prof_dir, + prof_dir.to_string(), ]); } NodeProfileType::Heap => { @@ -65,7 +65,7 @@ pub fn extend_task_command( "--heap-prof-name".into(), "snapshot.heapprofile".into(), "--heap-prof-dir".into(), - prof_dir, + prof_dir.to_string(), ]); } }; diff --git a/toolchains/node/tests/tier2_test.rs b/toolchains/node/tests/tier2_test.rs index 53984fd5..1ca92d27 100644 --- a/toolchains/node/tests/tier2_test.rs +++ b/toolchains/node/tests/tier2_test.rs @@ -1,9 +1,7 @@ -use moon_common::path::standardize_separators; use moon_pdk_api::*; use moon_pdk_test_utils::create_empty_moon_sandbox; use serde_json::json; use std::fs; -use std::path::PathBuf; mod node_toolchain_tier2 { use super::*; @@ -21,7 +19,7 @@ mod node_toolchain_tier2 { let output = plugin .setup_environment(SetupEnvironmentInput { - root: VirtualPath::Real(sandbox.path().into()), + root: VirtualPath::new(sandbox.path()), toolchain_config: json!({ "syncVersionManagerConfig": "nvm", "version": null @@ -41,7 +39,7 @@ mod node_toolchain_tier2 { let output = plugin .setup_environment(SetupEnvironmentInput { - root: VirtualPath::Real(sandbox.path().into()), + root: VirtualPath::new(sandbox.path()), toolchain_config: json!({ "syncVersionManagerConfig": null, "version": "20.1" @@ -61,7 +59,7 @@ mod node_toolchain_tier2 { let output = plugin .setup_environment(SetupEnvironmentInput { - root: VirtualPath::Real(sandbox.path().into()), + root: VirtualPath::new(sandbox.path()), toolchain_config: json!({ "syncVersionManagerConfig": "nvm", "version": "20.1" @@ -76,7 +74,10 @@ mod node_toolchain_tier2 { .iter() .any(|op| op.id == "sync-version-manager") ); - assert_eq!(output.changed_files, [PathBuf::from("/workspace/.nvmrc")]); + assert_eq!( + output.changed_files, + [VirtualPath::new("/workspace/.nvmrc")] + ); assert_eq!( fs::read_to_string(sandbox.path().join(".nvmrc")).unwrap(), "20.1" @@ -90,7 +91,7 @@ mod node_toolchain_tier2 { let output = plugin .setup_environment(SetupEnvironmentInput { - root: VirtualPath::Real(sandbox.path().into()), + root: VirtualPath::new(sandbox.path()), toolchain_config: json!({ "syncVersionManagerConfig": "nodenv", "version": "20.1" @@ -107,7 +108,7 @@ mod node_toolchain_tier2 { ); assert_eq!( output.changed_files, - [PathBuf::from("/workspace/.node-version")] + [VirtualPath::new("/workspace/.node-version")] ); assert_eq!( fs::read_to_string(sandbox.path().join(".node-version")).unwrap(), @@ -120,6 +121,15 @@ mod node_toolchain_tier2 { mod extend_task_command { use super::*; + // The guest converts virtual paths to real paths by joining the + // stripped virtual suffix onto the native host prefix with `/`, so on + // Windows the result is a mixed-separator string like + // `C:\...\sandbox/project/.moon`. Mirror that exactly, since args are + // compared as strings, not paths. + fn expected_prof_dir(root: &std::path::Path) -> String { + format!("{}/project/.moon", root.display()) + } + #[tokio::test(flavor = "multi_thread")] async fn prepends_exec_args_when_node() { let sandbox = create_empty_moon_sandbox(); @@ -181,7 +191,7 @@ mod node_toolchain_tier2 { "--cpu-prof-name".into(), "snapshot.cpuprofile".into(), "--cpu-prof-dir".into(), - standardize_separators(sandbox.path().join("project/.moon").to_string_lossy()) + expected_prof_dir(sandbox.path()) ]) ); } @@ -208,7 +218,7 @@ mod node_toolchain_tier2 { "--heap-prof-name".into(), "snapshot.heapprofile".into(), "--heap-prof-dir".into(), - standardize_separators(sandbox.path().join("project/.moon").to_string_lossy()) + expected_prof_dir(sandbox.path()) ]) ); } @@ -256,7 +266,7 @@ mod node_toolchain_tier2 { "--heap-prof-name".into(), "snapshot.heapprofile".into(), "--heap-prof-dir".into(), - standardize_separators(sandbox.path().join("project/.moon").to_string_lossy()) + expected_prof_dir(sandbox.path()) ]) ); } diff --git a/toolchains/python-pip/CHANGELOG.md b/toolchains/python-pip/CHANGELOG.md index 2e5ddcdd..e97d3b44 100644 --- a/toolchains/python-pip/CHANGELOG.md +++ b/toolchains/python-pip/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## Unreleased + +#### 🚀 Updates + +- Updated to support moon v2.5 release. + ## 0.1.2 #### 🚀 Updates diff --git a/toolchains/python-poetry/CHANGELOG.md b/toolchains/python-poetry/CHANGELOG.md index 1180103e..e4b5f181 100644 --- a/toolchains/python-poetry/CHANGELOG.md +++ b/toolchains/python-poetry/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## Unreleased + +#### 🚀 Updates + +- Updated to support moon v2.5 release. + ## 0.1.0 #### 🚀 Updates diff --git a/toolchains/python-uv/CHANGELOG.md b/toolchains/python-uv/CHANGELOG.md index df1a4ae5..ee8af633 100644 --- a/toolchains/python-uv/CHANGELOG.md +++ b/toolchains/python-uv/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## Unreleased + +#### 🚀 Updates + +- Updated to support moon v2.5 release. + ## 0.1.3 #### 🐞 Fixes diff --git a/toolchains/python/CHANGELOG.md b/toolchains/python/CHANGELOG.md index eee15142..439f9aed 100644 --- a/toolchains/python/CHANGELOG.md +++ b/toolchains/python/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## Unreleased + +#### 🚀 Updates + +- Updated to support moon v2.5 release. + ## 0.2.0 #### 🚀 Updates diff --git a/toolchains/python/src/managers/pip.rs b/toolchains/python/src/managers/pip.rs index ed4d5843..f86e3832 100644 --- a/toolchains/python/src/managers/pip.rs +++ b/toolchains/python/src/managers/pip.rs @@ -78,10 +78,7 @@ pub fn parse_requirements_txt(path: &VirtualPath, output: &mut ParseLockOutput) } if let Some(version) = config.version { - if matches!( - version, - UnresolvedVersionSpec::Semantic(_) | UnresolvedVersionSpec::Calendar(_) - ) { + if matches!(version, UnresolvedVersionSpec::Version(_)) { dep.version = Some(version.to_resolved_spec()); } else { dep.req = Some(version); @@ -107,7 +104,7 @@ pub fn parse_pyproject_toml(path: &VirtualPath, output: &mut ParseManifestOutput }; if let Some(version) = &project.version { - output.version = Version::parse(&version.to_string()).ok(); + output.version = Version::parse(version.to_string()).ok(); } if let Some(dependencies) = &project.dependencies { diff --git a/toolchains/python/src/tier2.rs b/toolchains/python/src/tier2.rs index 1b07f8a3..841364fd 100644 --- a/toolchains/python/src/tier2.rs +++ b/toolchains/python/src/tier2.rs @@ -3,6 +3,7 @@ use crate::managers::*; use crate::pyproject_toml::{PyProjectToml, PyProjectTomlWithTools, normalize_distribution_name}; use extism_pdk::*; use moon_config::DependencyScope; +use moon_pdk::VirtualPathExt; use moon_pdk::{ load_project_toolchain_config, load_toolchain_config, locate_root, locate_root_many, locate_root_many_with_check, parse_toolchain_config_schema, @@ -88,9 +89,7 @@ pub fn extend_project_graph( .extended_projects .insert(id.to_owned(), project_output); - if let Some(file) = manifest.path.virtual_path() { - output.input_files.push(file); - } + output.input_files.push(manifest.path.clone()); } Ok(Json(output)) @@ -102,10 +101,10 @@ fn gather_shared_paths( paths: &mut Vec, ) -> AnyResult<()> { if let Some(venv_parent) = locate_root(current_dir, &config.venv_name) - && let Some(venv_root) = venv_parent.join(&config.venv_name).real_path() + && let Some(venv_root) = venv_parent.join(&config.venv_name).to_real_path()? { - paths.push(venv_root.join("Scripts")); - paths.push(venv_root.join("bin")); + paths.push(venv_root.join("Scripts").to_path_buf()); + paths.push(venv_root.join("bin").to_path_buf()); } Ok(()) @@ -152,9 +151,9 @@ pub fn locate_dependencies_root( // First attempt: find lock files if let Some(root) = locate_root_many(&input.starting_dir, &lock_names) { - output.root = root.virtual_path(); output.members = PyProjectTomlWithTools::load(root.join("pyproject.toml"))? .extract_members(package_manager)?; + output.root = Some(root); } // Second attempt: find workspace-compatible manifest files @@ -166,7 +165,7 @@ pub fn locate_dependencies_root( if manifest.tool.is_some() && let Some(members) = manifest.extract_members(package_manager)? { - output.root = root.virtual_path(); + output.root = Some(root.to_owned()); output.members = Some(members); found = true; } @@ -176,10 +175,8 @@ pub fn locate_dependencies_root( } // Last attempt: find a manifest file (project only) - if output.root.is_none() - && let Some(root) = locate_root_many(&input.starting_dir, &manifest_names) - { - output.root = root.virtual_path(); + if output.root.is_none() { + output.root = locate_root_many(&input.starting_dir, &manifest_names); } Ok(Json(output)) diff --git a/toolchains/python/tests/tier2_test.rs b/toolchains/python/tests/tier2_test.rs index 3d0bc634..fa2f1838 100644 --- a/toolchains/python/tests/tier2_test.rs +++ b/toolchains/python/tests/tier2_test.rs @@ -3,7 +3,6 @@ use moon_pdk_api::*; use moon_pdk_test_utils::{create_empty_moon_sandbox, create_moon_sandbox}; use serde_json::json; use std::collections::BTreeMap; -use std::path::PathBuf; mod python_toolchain_tier2 { use super::*; @@ -62,14 +61,14 @@ mod python_toolchain_tier2 { ]) ); - output.input_files.sort(); + output.input_files.sort_by(|a, d| a.cmp(d)); assert_eq!( output.input_files, [ - PathBuf::from("/workspace/a/pyproject.toml"), - PathBuf::from("/workspace/b/pyproject.toml"), - PathBuf::from("/workspace/c/pyproject.toml"), + VirtualPath::new("/workspace/a/pyproject.toml"), + VirtualPath::new("/workspace/b/pyproject.toml"), + VirtualPath::new("/workspace/c/pyproject.toml"), ] ); } @@ -142,7 +141,7 @@ dependencies = ["internal-lib"] assert_eq!( output.input_files, - [PathBuf::from("/workspace/a/pyproject.toml")] + [VirtualPath::new("/workspace/a/pyproject.toml")] ); } @@ -273,7 +272,7 @@ dependencies = ["internal-lib"] let output = plugin .locate_dependencies_root(LocateDependenciesRootInput { - starting_dir: VirtualPath::Real(sandbox.path().into()), + starting_dir: VirtualPath::new(sandbox.path()), toolchain_config: json!({ "packageManager": "pip" }), @@ -292,7 +291,7 @@ dependencies = ["internal-lib"] let output = plugin .locate_dependencies_root(LocateDependenciesRootInput { - starting_dir: VirtualPath::Real(sandbox.path().join("package")), + starting_dir: VirtualPath::new(sandbox.path().join("package")), toolchain_config: json!({ "packageManager": null }), @@ -314,7 +313,7 @@ dependencies = ["internal-lib"] let output = plugin .locate_dependencies_root(LocateDependenciesRootInput { - starting_dir: VirtualPath::Real(sandbox.path().join("package/nested")), + starting_dir: VirtualPath::new(sandbox.path().join("package/nested")), toolchain_config: json!({ "packageManager": "pip" }), @@ -323,7 +322,7 @@ dependencies = ["internal-lib"] .await; assert!(output.members.is_none()); - assert_eq!(output.root.unwrap(), PathBuf::from("/workspace/package")); + assert_eq!(output.root.unwrap(), VirtualPath::new("/workspace/package")); } #[tokio::test(flavor = "multi_thread")] @@ -335,7 +334,7 @@ dependencies = ["internal-lib"] let output = plugin .locate_dependencies_root(LocateDependenciesRootInput { - starting_dir: VirtualPath::Real(sandbox.path().join("package/nested")), + starting_dir: VirtualPath::new(sandbox.path().join("package/nested")), toolchain_config: json!({ "packageManager": "pip" }), @@ -344,7 +343,7 @@ dependencies = ["internal-lib"] .await; assert!(output.members.is_none()); - assert_eq!(output.root.unwrap(), PathBuf::from("/workspace/package")); + assert_eq!(output.root.unwrap(), VirtualPath::new("/workspace/package")); } #[tokio::test(flavor = "multi_thread")] @@ -354,7 +353,7 @@ dependencies = ["internal-lib"] let output = plugin .locate_dependencies_root(LocateDependenciesRootInput { - starting_dir: VirtualPath::Real( + starting_dir: VirtualPath::new( sandbox.path().join("workspace/packages/a/nested"), ), toolchain_config: json!({ @@ -367,7 +366,7 @@ dependencies = ["internal-lib"] assert!(output.members.is_none()); assert_eq!( output.root.unwrap(), - PathBuf::from("/workspace/workspace/packages/a") + VirtualPath::new("/workspace/workspace/packages/a") ); } } @@ -384,7 +383,7 @@ dependencies = ["internal-lib"] let output = plugin .locate_dependencies_root(LocateDependenciesRootInput { - starting_dir: VirtualPath::Real(sandbox.path().join("package/nested")), + starting_dir: VirtualPath::new(sandbox.path().join("package/nested")), toolchain_config: json!({ "packageManager": "poetry" }), @@ -393,7 +392,7 @@ dependencies = ["internal-lib"] .await; assert!(output.members.is_none()); - assert_eq!(output.root.unwrap(), PathBuf::from("/workspace/package")); + assert_eq!(output.root.unwrap(), VirtualPath::new("/workspace/package")); } #[tokio::test(flavor = "multi_thread")] @@ -406,7 +405,7 @@ dependencies = ["internal-lib"] let output = plugin .locate_dependencies_root(LocateDependenciesRootInput { - starting_dir: VirtualPath::Real(sandbox.path().join("package/nested")), + starting_dir: VirtualPath::new(sandbox.path().join("package/nested")), toolchain_config: json!({ "packageManager": "poetry" }), @@ -415,7 +414,7 @@ dependencies = ["internal-lib"] .await; assert!(output.members.is_none()); - assert_eq!(output.root.unwrap(), PathBuf::from("/workspace/package")); + assert_eq!(output.root.unwrap(), VirtualPath::new("/workspace/package")); } } @@ -429,7 +428,7 @@ dependencies = ["internal-lib"] let output = plugin .locate_dependencies_root(LocateDependenciesRootInput { - starting_dir: VirtualPath::Real(sandbox.path().join("package/nested")), + starting_dir: VirtualPath::new(sandbox.path().join("package/nested")), toolchain_config: json!({ "packageManager": "uv" }), @@ -438,7 +437,7 @@ dependencies = ["internal-lib"] .await; assert!(output.members.is_none()); - assert_eq!(output.root.unwrap(), PathBuf::from("/workspace/package")); + assert_eq!(output.root.unwrap(), VirtualPath::new("/workspace/package")); } #[tokio::test(flavor = "multi_thread")] @@ -450,7 +449,7 @@ dependencies = ["internal-lib"] let output = plugin .locate_dependencies_root(LocateDependenciesRootInput { - starting_dir: VirtualPath::Real(sandbox.path().join("package/nested")), + starting_dir: VirtualPath::new(sandbox.path().join("package/nested")), toolchain_config: json!({ "packageManager": "uv" }), @@ -459,7 +458,7 @@ dependencies = ["internal-lib"] .await; assert!(output.members.is_none()); - assert_eq!(output.root.unwrap(), PathBuf::from("/workspace/package")); + assert_eq!(output.root.unwrap(), VirtualPath::new("/workspace/package")); } #[tokio::test(flavor = "multi_thread")] @@ -469,7 +468,7 @@ dependencies = ["internal-lib"] let output = plugin .locate_dependencies_root(LocateDependenciesRootInput { - starting_dir: VirtualPath::Real( + starting_dir: VirtualPath::new( sandbox.path().join("workspace/packages/a/nested"), ), toolchain_config: json!({ @@ -480,7 +479,10 @@ dependencies = ["internal-lib"] .await; assert_eq!(output.members.unwrap(), ["packages/*"]); - assert_eq!(output.root.unwrap(), PathBuf::from("/workspace/workspace")); + assert_eq!( + output.root.unwrap(), + VirtualPath::new("/workspace/workspace") + ); } #[tokio::test(flavor = "multi_thread")] @@ -492,7 +494,7 @@ dependencies = ["internal-lib"] let output = plugin .locate_dependencies_root(LocateDependenciesRootInput { - starting_dir: VirtualPath::Real( + starting_dir: VirtualPath::new( sandbox.path().join("workspace/packages/a/nested"), ), toolchain_config: json!({ @@ -503,7 +505,10 @@ dependencies = ["internal-lib"] .await; assert_eq!(output.members.unwrap(), ["packages/*"]); - assert_eq!(output.root.unwrap(), PathBuf::from("/workspace/workspace")); + assert_eq!( + output.root.unwrap(), + VirtualPath::new("/workspace/workspace") + ); } } } @@ -518,7 +523,7 @@ dependencies = ["internal-lib"] let output = plugin .install_dependencies(InstallDependenciesInput { - root: VirtualPath::Real(sandbox.path().into()), + root: VirtualPath::new(sandbox.path()), toolchain_config: json!({}), ..Default::default() }) @@ -541,7 +546,7 @@ dependencies = ["internal-lib"] working_dir: plugin.plugin.to_virtual_path(sandbox.path()), ..Default::default() }, - root: VirtualPath::Real(sandbox.path().into()), + root: VirtualPath::new(sandbox.path()), toolchain_config: json!({ "packageManager": "pip" }), @@ -579,7 +584,7 @@ dependencies = ["internal-lib"] working_dir: plugin.plugin.to_virtual_path(sandbox.path()), ..Default::default() }, - root: VirtualPath::Real(sandbox.path().into()), + root: VirtualPath::new(sandbox.path()), toolchain_config: json!({ "packageManager": "pip" }), @@ -615,7 +620,7 @@ dependencies = ["internal-lib"] working_dir: plugin.plugin.to_virtual_path(sandbox.path()), ..Default::default() }, - root: VirtualPath::Real(sandbox.path().into()), + root: VirtualPath::new(sandbox.path()), toolchain_config: json!({ "packageManager": "pip" }), @@ -652,7 +657,7 @@ dependencies = ["internal-lib"] working_dir: plugin.plugin.to_virtual_path(sandbox.path()), ..Default::default() }, - root: VirtualPath::Real(sandbox.path().into()), + root: VirtualPath::new(sandbox.path()), toolchain_config: json!({ "packageManager": "pip" }), @@ -685,7 +690,7 @@ dependencies = ["internal-lib"] working_dir: plugin.plugin.to_virtual_path(sandbox.path()), ..Default::default() }, - root: VirtualPath::Real(sandbox.path().into()), + root: VirtualPath::new(sandbox.path()), toolchain_config: json!({ "packageManager": "poetry" }), @@ -718,7 +723,7 @@ dependencies = ["internal-lib"] working_dir: plugin.plugin.to_virtual_path(sandbox.path()), ..Default::default() }, - root: VirtualPath::Real(sandbox.path().into()), + root: VirtualPath::new(sandbox.path()), toolchain_config: json!({ "packageManager": "poetry" }), @@ -756,7 +761,7 @@ dependencies = ["internal-lib"] working_dir: plugin.plugin.to_virtual_path(sandbox.path()), ..Default::default() }, - root: VirtualPath::Real(sandbox.path().into()), + root: VirtualPath::new(sandbox.path()), toolchain_config: json!({ "packageManager": "poetry" }), @@ -790,7 +795,7 @@ dependencies = ["internal-lib"] working_dir: plugin.plugin.to_virtual_path(sandbox.path()), ..Default::default() }, - root: VirtualPath::Real(sandbox.path().into()), + root: VirtualPath::new(sandbox.path()), toolchain_config: json!({ "packageManager": "uv" }), @@ -824,7 +829,7 @@ dependencies = ["internal-lib"] working_dir: plugin.plugin.to_virtual_path(sandbox.path()), ..Default::default() }, - root: VirtualPath::Real(sandbox.path().into()), + root: VirtualPath::new(sandbox.path()), toolchain_config: json!({ "packageManager": "uv" }), @@ -859,7 +864,7 @@ dependencies = ["internal-lib"] working_dir: plugin.plugin.to_virtual_path(sandbox.path()), ..Default::default() }, - root: VirtualPath::Real(sandbox.path().into()), + root: VirtualPath::new(sandbox.path()), toolchain_config: json!({ "packageManager": "uv", "version": "1.2.3" @@ -906,7 +911,7 @@ dependencies = ["internal-lib"] working_dir: plugin.plugin.to_virtual_path(sandbox.path()), ..Default::default() }, - root: VirtualPath::Real(sandbox.path().into()), + root: VirtualPath::new(sandbox.path()), toolchain_config: json!({ "packageManager": "uv" }), @@ -940,7 +945,7 @@ dependencies = ["internal-lib"] working_dir: plugin.plugin.to_virtual_path(sandbox.path()), ..Default::default() }, - root: VirtualPath::Real(sandbox.path().into()), + root: VirtualPath::new(sandbox.path()), toolchain_config: json!({ "packageManager": "uv" }), @@ -988,7 +993,7 @@ dependencies = ["internal-lib"] working_dir: plugin.plugin.to_virtual_path(sandbox.path()), ..Default::default() }, - root: VirtualPath::Real(sandbox.path().into()), + root: VirtualPath::new(sandbox.path()), toolchain_config: json!({ "packageManager": "uv-pip" }), @@ -1031,7 +1036,7 @@ dependencies = ["internal-lib"] working_dir: plugin.plugin.to_virtual_path(sandbox.path()), ..Default::default() }, - root: VirtualPath::Real(sandbox.path().into()), + root: VirtualPath::new(sandbox.path()), toolchain_config: json!({ "packageManager": "uv-pip" }), project: Some(ProjectFragment { id: Id::raw("workspace"), @@ -1066,7 +1071,7 @@ dependencies = ["internal-lib"] working_dir: plugin.plugin.to_virtual_path(sandbox.path()), ..Default::default() }, - root: VirtualPath::Real(sandbox.path().into()), + root: VirtualPath::new(sandbox.path()), toolchain_config: json!({ "packageManager": "uv-pip" }), project: Some(ProjectFragment { id: Id::raw("workspace"), @@ -1107,7 +1112,7 @@ dependencies = ["internal-lib"] let output = plugin .parse_lock(ParseLockInput { - path: VirtualPath::Real(sandbox.path().join("pylock.toml")), + path: VirtualPath::new(sandbox.path().join("pylock.toml")), ..Default::default() }) .await; @@ -1182,7 +1187,7 @@ files = [] let output = plugin .parse_lock(ParseLockInput { - path: VirtualPath::Real(sandbox.path().join("poetry.lock")), + path: VirtualPath::new(sandbox.path().join("poetry.lock")), ..Default::default() }) .await; @@ -1219,7 +1224,7 @@ files = [] let output = plugin .parse_lock(ParseLockInput { - path: VirtualPath::Real(sandbox.path().join("requirements.txt")), + path: VirtualPath::new(sandbox.path().join("requirements.txt")), ..Default::default() }) .await; @@ -1240,6 +1245,7 @@ files = [] "requests".into(), vec![LockDependency { meta: Some("security".into()), + req: Some(UnresolvedVersionSpec::parse("==2.8.*, >=2.8.1").unwrap()), ..Default::default() }] ), @@ -1255,7 +1261,7 @@ files = [] let output = plugin .parse_lock(ParseLockInput { - path: VirtualPath::Real(sandbox.path().join("uv.lock")), + path: VirtualPath::new(sandbox.path().join("uv.lock")), ..Default::default() }) .await; @@ -1300,7 +1306,7 @@ files = [] let output = plugin .parse_manifest(ParseManifestInput { - path: VirtualPath::Real(sandbox.path().join("pyproject.toml")), + path: VirtualPath::new(sandbox.path().join("pyproject.toml")), ..Default::default() }) .await; @@ -1348,7 +1354,7 @@ files = [] let output = plugin .parse_manifest(ParseManifestInput { - path: VirtualPath::Real(sandbox.path().join("requirements.in")), + path: VirtualPath::new(sandbox.path().join("requirements.in")), ..Default::default() }) .await; @@ -1375,6 +1381,9 @@ files = [] "requests".into(), ManifestDependency::Config(ManifestDependencyConfig { features: vec!["security".into()], + version: Some( + UnresolvedVersionSpec::parse("==2.8.*, >=2.8.1").unwrap() + ), ..Default::default() }) ), @@ -1403,7 +1412,7 @@ files = [] let output = plugin .setup_environment(SetupEnvironmentInput { - root: VirtualPath::Real(sandbox.path().into()), + root: VirtualPath::new(sandbox.path()), toolchain_config: json!({ "packageManager": null }), @@ -1421,7 +1430,7 @@ files = [] let output = plugin .setup_environment(SetupEnvironmentInput { - root: VirtualPath::Real(sandbox.path().into()), + root: VirtualPath::new(sandbox.path()), toolchain_config: json!({ "packageManager": "uv", "venvName": ".virtual-env" @@ -1451,7 +1460,7 @@ files = [] let output = plugin .setup_environment(SetupEnvironmentInput { - root: VirtualPath::Real(sandbox.path().into()), + root: VirtualPath::new(sandbox.path()), toolchain_config: json!({ "packageManager": "pip" }), @@ -1484,7 +1493,7 @@ files = [] let plugin = sandbox.create_toolchain("python").await; let output = plugin .setup_environment(SetupEnvironmentInput { - root: VirtualPath::Real(sandbox.path().into()), + root: VirtualPath::new(sandbox.path()), toolchain_config: json!({ "packageManager": "pip" }), @@ -1513,7 +1522,7 @@ files = [] let output = plugin .setup_environment(SetupEnvironmentInput { - root: VirtualPath::Real(sandbox.path().into()), + root: VirtualPath::new(sandbox.path()), toolchain_config: json!({ "packageManager": "poetry" }), @@ -1546,7 +1555,7 @@ files = [] let plugin = sandbox.create_toolchain("python").await; let output = plugin .setup_environment(SetupEnvironmentInput { - root: VirtualPath::Real(sandbox.path().into()), + root: VirtualPath::new(sandbox.path()), toolchain_config: json!({ "packageManager": "poetry" }), @@ -1575,7 +1584,7 @@ files = [] let output = plugin .setup_environment(SetupEnvironmentInput { - root: VirtualPath::Real(sandbox.path().into()), + root: VirtualPath::new(sandbox.path()), toolchain_config: json!({ "packageManager": "uv" }), @@ -1604,7 +1613,7 @@ files = [] let output = plugin .setup_environment(SetupEnvironmentInput { - root: VirtualPath::Real(sandbox.path().into()), + root: VirtualPath::new(sandbox.path()), toolchain_config: json!({ "packageManager": "uv", "version": "1.2.3" @@ -1647,7 +1656,7 @@ files = [] let plugin = sandbox.create_toolchain("python").await; let output = plugin .setup_environment(SetupEnvironmentInput { - root: VirtualPath::Real(sandbox.path().into()), + root: VirtualPath::new(sandbox.path()), toolchain_config: json!({ "packageManager": "uv" }), @@ -1676,7 +1685,7 @@ files = [] let output = plugin .setup_environment(SetupEnvironmentInput { - root: VirtualPath::Real(sandbox.path().into()), + root: VirtualPath::new(sandbox.path()), toolchain_config: json!({ "packageManager": "uv", "version": "3.12.0" @@ -1714,7 +1723,7 @@ files = [] let plugin = sandbox.create_toolchain("python").await; let output = plugin .setup_environment(SetupEnvironmentInput { - root: VirtualPath::Real(sandbox.path().into()), + root: VirtualPath::new(sandbox.path()), toolchain_config: json!({ "packageManager": "uv-pip" }), project: Some(ProjectFragment { id: Id::raw("workspace"), @@ -1739,7 +1748,7 @@ files = [] let plugin = sandbox.create_toolchain("python").await; let output = plugin .setup_environment(SetupEnvironmentInput { - root: VirtualPath::Real(sandbox.path().into()), + root: VirtualPath::new(sandbox.path()), toolchain_config: json!({ "packageManager": "uv-pip", "version": "3.12.0" }), project: Some(ProjectFragment { id: Id::raw("workspace"), @@ -1776,7 +1785,7 @@ files = [] let output = plugin .setup_environment(SetupEnvironmentInput { - root: VirtualPath::Real(sandbox.path().into()), + root: VirtualPath::new(sandbox.path()), toolchain_config: json!({ "packageManager": "uv" }), @@ -1801,7 +1810,7 @@ files = [] let output = plugin .setup_environment(SetupEnvironmentInput { - root: VirtualPath::Real(sandbox.path().into()), + root: VirtualPath::new(sandbox.path()), toolchain_config: json!({ "packageManager": "pip" }), @@ -1826,7 +1835,7 @@ files = [] let output = plugin .setup_environment(SetupEnvironmentInput { - root: VirtualPath::Real(sandbox.path().into()), + root: VirtualPath::new(sandbox.path()), toolchain_config: json!({ "packageManager": "uv-pip" }), @@ -1851,7 +1860,7 @@ files = [] let output = plugin .setup_environment(SetupEnvironmentInput { - root: VirtualPath::Real(sandbox.path().into()), + root: VirtualPath::new(sandbox.path()), toolchain_config: json!({ "packageManager": "uv", "venvName": ".virtual-env" @@ -1880,7 +1889,7 @@ files = [] let plugin = sandbox.create_toolchain("python").await; let output = plugin .setup_environment(SetupEnvironmentInput { - root: VirtualPath::Real(sandbox.path().into()), + root: VirtualPath::new(sandbox.path()), toolchain_config: json!({ "packageManager": "uv" }), @@ -1914,7 +1923,7 @@ files = [] let plugin = sandbox.create_toolchain("python").await; let output = plugin .setup_environment(SetupEnvironmentInput { - root: VirtualPath::Real(sandbox.path().into()), + root: VirtualPath::new(sandbox.path()), toolchain_config: json!({ "packageManager": "pip" }), diff --git a/toolchains/ruby/CHANGELOG.md b/toolchains/ruby/CHANGELOG.md index e1871b91..3ad69162 100644 --- a/toolchains/ruby/CHANGELOG.md +++ b/toolchains/ruby/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## Unreleased + +#### 🚀 Updates + +- Updated to support moon v2.5 release. + ## 0.1.0 #### 🚀 Updates diff --git a/toolchains/ruby/src/tier2.rs b/toolchains/ruby/src/tier2.rs index ca03655d..e361e111 100644 --- a/toolchains/ruby/src/tier2.rs +++ b/toolchains/ruby/src/tier2.rs @@ -2,7 +2,7 @@ use crate::config::RubyToolchainConfig; use crate::gemfile; use extism_pdk::*; use moon_config::{DependencyScope, VersionSpec}; -use moon_pdk::{locate_root_many, parse_toolchain_config_schema}; +use moon_pdk::{VirtualPathExt, locate_root_many, parse_toolchain_config_schema}; use moon_pdk_api::*; use rubund::parser::{SourceType, parse_lockfile}; use starbase_utils::fs; @@ -18,17 +18,14 @@ const DEFAULT_NON_PRODUCTION_GROUPS: [&str; 2] = ["development", "test"]; pub fn locate_dependencies_root( Json(input): Json, ) -> FnResult> { - let mut output = LocateDependenciesRootOutput::default(); - // Bundler has no workspace concept (unlike npm/uv/Cargo), so each project // is its own dependency root and there are no members to report. Walk // upward for the nearest Gemfile/Gemfile.lock. If none is found, `root` // stays `None` and moon skips install steps for this project. - if let Some(root) = locate_root_many(&input.starting_dir, &ROOT_FILES) { - output.root = root.virtual_path(); - } - - Ok(Json(output)) + Ok(Json(LocateDependenciesRootOutput { + root: locate_root_many(&input.starting_dir, &ROOT_FILES), + ..Default::default() + })) } #[plugin_fn] @@ -113,9 +110,9 @@ pub fn extend_command( // PATH so tasks can invoke e.g. `rspec` directly. `bundle exec ` works // regardless, via the `bundle` binary provided by the toolchain (tier 3). if let Some(root) = locate_root_many(&input.current_dir, &ROOT_FILES) - && let Some(real) = root.real_path() + && let Some(real) = root.to_real_path()? { - output.paths.push(real.join("bin")); + output.paths.push(real.join("bin").to_path_buf()); } Ok(Json(output)) @@ -266,9 +263,7 @@ pub fn extend_project_graph( output.extended_projects.insert(id.clone(), project_output); } - if let Some(file) = gemfile_path.virtual_path() { - output.input_files.push(file); - } + output.input_files.push(gemfile_path); } Ok(Json(output)) diff --git a/toolchains/ruby/tests/tier2_test.rs b/toolchains/ruby/tests/tier2_test.rs index d03cf870..320abca4 100644 --- a/toolchains/ruby/tests/tier2_test.rs +++ b/toolchains/ruby/tests/tier2_test.rs @@ -17,7 +17,7 @@ mod ruby_toolchain_tier2 { let output = plugin .locate_dependencies_root(LocateDependenciesRootInput { - starting_dir: VirtualPath::Real(sandbox.path().into()), + starting_dir: VirtualPath::new(sandbox.path()), toolchain_config: json!({}), ..Default::default() }) @@ -35,7 +35,7 @@ mod ruby_toolchain_tier2 { let output = plugin .locate_dependencies_root(LocateDependenciesRootInput { - starting_dir: VirtualPath::Real(sandbox.path().join("app")), + starting_dir: VirtualPath::new(sandbox.path().join("app")), toolchain_config: json!({}), ..Default::default() }) @@ -43,7 +43,7 @@ mod ruby_toolchain_tier2 { // Bundler has no workspaces, so no members are reported. assert!(output.members.is_none()); - assert_eq!(output.root.unwrap(), PathBuf::from("/workspace/app")); + assert_eq!(output.root.unwrap(), VirtualPath::new("/workspace/app")); } #[tokio::test(flavor = "multi_thread")] @@ -54,13 +54,13 @@ mod ruby_toolchain_tier2 { let output = plugin .locate_dependencies_root(LocateDependenciesRootInput { - starting_dir: VirtualPath::Real(sandbox.path().join("app/lib/nested")), + starting_dir: VirtualPath::new(sandbox.path().join("app/lib/nested")), toolchain_config: json!({}), ..Default::default() }) .await; - assert_eq!(output.root.unwrap(), PathBuf::from("/workspace/app")); + assert_eq!(output.root.unwrap(), VirtualPath::new("/workspace/app")); } } @@ -74,7 +74,7 @@ mod ruby_toolchain_tier2 { let output = plugin .setup_environment(SetupEnvironmentInput { - root: VirtualPath::Real(sandbox.path().into()), + root: VirtualPath::new(sandbox.path()), toolchain_config: json!({}), ..Default::default() }) @@ -99,7 +99,7 @@ mod ruby_toolchain_tier2 { let output = plugin .setup_environment(SetupEnvironmentInput { - root: VirtualPath::Real(sandbox.path().into()), + root: VirtualPath::new(sandbox.path()), toolchain_config: json!({ "bundlePath": "vendor/gems" }), ..Default::default() }) @@ -124,7 +124,7 @@ mod ruby_toolchain_tier2 { let output = plugin .setup_environment(SetupEnvironmentInput { - root: VirtualPath::Real(sandbox.path().into()), + root: VirtualPath::new(sandbox.path()), toolchain_config: json!({ "frozen": true }), ..Default::default() }) @@ -159,7 +159,7 @@ mod ruby_toolchain_tier2 { let output = plugin .setup_environment(SetupEnvironmentInput { - root: VirtualPath::Real(sandbox.path().into()), + root: VirtualPath::new(sandbox.path()), toolchain_config: json!({}), ..Default::default() }) @@ -179,7 +179,7 @@ mod ruby_toolchain_tier2 { let output = plugin .install_dependencies(InstallDependenciesInput { - root: VirtualPath::Real(sandbox.path().into()), + root: VirtualPath::new(sandbox.path()), toolchain_config: json!({}), ..Default::default() }) @@ -202,7 +202,7 @@ mod ruby_toolchain_tier2 { let output = plugin .install_dependencies(InstallDependenciesInput { - root: VirtualPath::Real(sandbox.path().into()), + root: VirtualPath::new(sandbox.path()), toolchain_config: json!({}), production: true, ..Default::default() @@ -225,7 +225,7 @@ mod ruby_toolchain_tier2 { let output = plugin .install_dependencies(InstallDependenciesInput { - root: VirtualPath::Real(sandbox.path().into()), + root: VirtualPath::new(sandbox.path()), toolchain_config: json!({ "bundlerInstallArgs": ["--jobs", "4"] }), ..Default::default() }) @@ -313,7 +313,7 @@ BUNDLED WITH let output = plugin .parse_lock(ParseLockInput { - path: VirtualPath::Real(sandbox.path().join("Gemfile.lock")), + path: VirtualPath::new(sandbox.path().join("Gemfile.lock")), ..Default::default() }) .await; @@ -365,7 +365,7 @@ BUNDLED WITH let output = plugin .parse_lock(ParseLockInput { - path: VirtualPath::Real(sandbox.path().join("Gemfile.lock")), + path: VirtualPath::new(sandbox.path().join("Gemfile.lock")), ..Default::default() }) .await; @@ -415,7 +415,7 @@ end let output = plugin .parse_manifest(ParseManifestInput { - path: VirtualPath::Real(sandbox.path().join("Gemfile")), + path: VirtualPath::new(sandbox.path().join("Gemfile")), ..Default::default() }) .await; diff --git a/toolchains/rust/CHANGELOG.md b/toolchains/rust/CHANGELOG.md index 4041564f..1dc24027 100644 --- a/toolchains/rust/CHANGELOG.md +++ b/toolchains/rust/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## Unreleased + +#### 🚀 Updates + +- Updated to support moon v2.5 release. + ## 1.0.7 #### 🐞 Fixes diff --git a/toolchains/rust/src/tier1.rs b/toolchains/rust/src/tier1.rs index b4c219d0..2e94ca2f 100644 --- a/toolchains/rust/src/tier1.rs +++ b/toolchains/rust/src/tier1.rs @@ -97,13 +97,8 @@ pub fn scaffold_docker( fs::write_file(&lib_file, "")?; fs::write_file(&main_file, "")?; - if let Some(file) = lib_file.virtual_path() { - output.copied_files.push(file); - } - - if let Some(file) = main_file.virtual_path() { - output.copied_files.push(file); - } + output.copied_files.push(lib_file.clone()); + output.copied_files.push(main_file.clone()); } // When we copy sources, we then need to remove these files @@ -182,15 +177,13 @@ pub fn prune_docker(Json(input): Json) -> FnResult, ) -> AnyResult<()> { if let Some(globals_dir) = globals_dir - && globals_dir.real_path().is_some() + && globals_dir.to_real_path()?.is_some() { // Avoid the host env overhead if we already // have a valid globals directory! @@ -97,7 +97,11 @@ fn gather_shared_paths( } else if let Some(value) = get_host_env_var("CARGO_HOME")? { Some(PathBuf::from(value).join("bin")) } else { - env.home_dir.join(".cargo").join("bin").real_path() + env.home_dir + .join(".cargo") + .join("bin") + .to_real_path()? + .map(|path| path.to_path_buf()) }; if let Some(dir) = maybe_dir { @@ -140,7 +144,7 @@ pub fn extend_task_command( } // Always include Cargo specific paths for all commands - gather_shared_paths(&env, input.globals_dir.as_ref(), &mut output.paths)?; + gather_shared_paths(env, input.globals_dir.as_ref(), &mut output.paths)?; Ok(Json(output)) } @@ -153,7 +157,7 @@ pub fn extend_task_script( let env = get_host_environment()?; // Always include Cargo specific paths for all commands - gather_shared_paths(&env, input.globals_dir.as_ref(), &mut output.paths)?; + gather_shared_paths(env, input.globals_dir.as_ref(), &mut output.paths)?; Ok(Json(output)) } @@ -166,8 +170,8 @@ pub fn locate_dependencies_root( // Attempt to find `Cargo.lock` first if let Some(root) = locate_root(&input.starting_dir, "Cargo.lock") { - output.root = root.virtual_path(); output.members = CargoToml::load(root.join("Cargo.toml"))?.extract_members(); + output.root = Some(root); } // Otherwise find a `Cargo.toml` workspace @@ -177,7 +181,7 @@ pub fn locate_dependencies_root( let mut found = false; if manifest.workspace.is_some() { - output.root = root.virtual_path(); + output.root = Some(root.to_owned()); output.members = manifest.extract_members(); found = true; } @@ -193,7 +197,7 @@ pub fn locate_dependencies_root( let mut found = false; if manifest.package.is_some() { - output.root = root.virtual_path(); + output.root = Some(root.to_owned()); found = true; } diff --git a/toolchains/rust/src/tier2_env.rs b/toolchains/rust/src/tier2_env.rs index 922a60be..4f8260bc 100644 --- a/toolchains/rust/src/tier2_env.rs +++ b/toolchains/rust/src/tier2_env.rs @@ -25,9 +25,7 @@ pub fn setup_environment( })?; output.operations.push(op); - output - .changed_files - .extend(files.into_iter().filter_map(|file| file.virtual_path())); + output.changed_files.extend(files); } // Sync `rust-toolchain.toml` toolchain @@ -37,9 +35,7 @@ pub fn setup_environment( })?; output.operations.push(op); - output - .changed_files - .extend(files.into_iter().filter_map(|file| file.virtual_path())); + output.changed_files.extend(files); } // Install components @@ -47,9 +43,11 @@ pub fn setup_environment( let mut args = vec!["component", "add"]; args.extend(config.components.iter().map(|c| c.as_str())); - output - .commands - .push(create_command("rustup", args, &input.root).cache("rustup-component-add")); + output.commands.push( + create_command("rustup", args, &input.root) + .cache(CacheStrategy::Memory) + .label("rustup-component-add"), + ); } // Install targets @@ -57,9 +55,11 @@ pub fn setup_environment( let mut args = vec!["target", "add"]; args.extend(config.targets.iter().map(|c| c.as_str())); - output - .commands - .push(create_command("rustup", args, &input.root).cache("rustup-target-add")); + output.commands.push( + create_command("rustup", args, &input.root) + .cache(CacheStrategy::Memory) + .label("rustup-target-add"), + ); } // Install binaries @@ -83,7 +83,8 @@ pub fn setup_environment( vec!["install", &binstall_package, "--force", "--locked"], &input.root, ) - .cache("cargo-binstall"), + .cache(CacheStrategy::Memory) + .label("cargo-binstall"), ); } @@ -111,18 +112,22 @@ pub fn setup_environment( let mut args = vec!["binstall", "--no-confirm", "--log-level", "info", "--force"]; args.extend(force_bins); - output - .commands - .push(create_command("cargo", args, &input.root).cache("cargo-bins-forced")); + output.commands.push( + create_command("cargo", args, &input.root) + .cache(CacheStrategy::Memory) + .label("cargo-bins-forced"), + ); } if !non_force_bins.is_empty() { let mut args = vec!["binstall", "--no-confirm", "--log-level", "info"]; args.extend(non_force_bins); - output - .commands - .push(create_command("cargo", args, &input.root).cache("cargo-bins")); + output.commands.push( + create_command("cargo", args, &input.root) + .cache(CacheStrategy::Memory) + .label("cargo-bins"), + ); } } diff --git a/toolchains/rust/tests/cargo_toml_test.rs b/toolchains/rust/tests/cargo_toml_test.rs index 64dae856..33d72101 100644 --- a/toolchains/rust/tests/cargo_toml_test.rs +++ b/toolchains/rust/tests/cargo_toml_test.rs @@ -11,7 +11,7 @@ mod cargo_toml { #[test] fn adds_if_not_set() { let mut manifest = CargoToml { - path: VirtualPath::Real(PathBuf::from("/base/Cargo.toml")), + path: VirtualPath::new(PathBuf::from("/base/Cargo.toml")), data: CargoTomlInner::new_package(), ..Default::default() }; @@ -30,7 +30,7 @@ mod cargo_toml { #[test] fn doesnt_add_if_empty() { let mut manifest = CargoToml { - path: VirtualPath::Real(PathBuf::from("/base/Cargo.toml")), + path: VirtualPath::new(PathBuf::from("/base/Cargo.toml")), data: CargoTomlInner::new_package(), ..Default::default() }; @@ -46,7 +46,7 @@ mod cargo_toml { #[test] fn doesnt_add_if_same_value() { let mut manifest = CargoToml { - path: VirtualPath::Real(PathBuf::from("/base/Cargo.toml")), + path: VirtualPath::new(PathBuf::from("/base/Cargo.toml")), data: { let mut pkg = CargoTomlInner::new_package(); pkg.package @@ -75,7 +75,7 @@ mod cargo_toml { #[test] fn saves_field_if_set() { let mut manifest = CargoToml { - path: VirtualPath::Real(PathBuf::from("/base/Cargo.toml")), + path: VirtualPath::new(PathBuf::from("/base/Cargo.toml")), data: CargoTomlInner::new_package(), ..Default::default() }; @@ -97,7 +97,7 @@ mod cargo_toml { #[test] fn doesnt_save_field_if_not_set() { let manifest = CargoToml { - path: VirtualPath::Real(PathBuf::from("/base/Cargo.toml")), + path: VirtualPath::new(PathBuf::from("/base/Cargo.toml")), data: CargoTomlInner::new_package(), ..Default::default() }; @@ -129,7 +129,7 @@ mod cargo_toml { #[test] fn adds_if_not_set() { let mut manifest = CargoToml { - path: VirtualPath::Real(PathBuf::from("/base/Cargo.toml")), + path: VirtualPath::new(PathBuf::from("/base/Cargo.toml")), data: CargoTomlInner::new_workspace(), ..Default::default() }; @@ -145,7 +145,7 @@ mod cargo_toml { #[test] fn doesnt_add_if_empty() { let mut manifest = CargoToml { - path: VirtualPath::Real(PathBuf::from("/base/Cargo.toml")), + path: VirtualPath::new(PathBuf::from("/base/Cargo.toml")), data: CargoTomlInner::new_workspace(), ..Default::default() }; @@ -161,7 +161,7 @@ mod cargo_toml { #[test] fn doesnt_add_if_same_value() { let mut manifest = CargoToml { - path: VirtualPath::Real(PathBuf::from("/base/Cargo.toml")), + path: VirtualPath::new(PathBuf::from("/base/Cargo.toml")), data: { let mut ws = CargoTomlInner::new_workspace(); let pkg = ws @@ -188,7 +188,7 @@ mod cargo_toml { #[test] fn saves_field_if_set() { let mut manifest = CargoToml { - path: VirtualPath::Real(PathBuf::from("/base/Cargo.toml")), + path: VirtualPath::new(PathBuf::from("/base/Cargo.toml")), data: CargoTomlInner::new_workspace(), ..Default::default() }; @@ -210,7 +210,7 @@ mod cargo_toml { #[test] fn doesnt_save_field_if_not_set() { let manifest = CargoToml { - path: VirtualPath::Real(PathBuf::from("/base/Cargo.toml")), + path: VirtualPath::new(PathBuf::from("/base/Cargo.toml")), data: CargoTomlInner::new_workspace(), ..Default::default() }; diff --git a/toolchains/rust/tests/tier1_test.rs b/toolchains/rust/tests/tier1_test.rs index 4dcce4d7..2834268c 100644 --- a/toolchains/rust/tests/tier1_test.rs +++ b/toolchains/rust/tests/tier1_test.rs @@ -3,7 +3,6 @@ use moon_pdk_api::*; use moon_pdk_test_utils::{create_empty_moon_sandbox, create_moon_sandbox}; use serde_json::json; use std::fs; -use std::path::PathBuf; mod rust_toolchain_tier1 { use super::*; @@ -52,8 +51,8 @@ mod rust_toolchain_tier1 { let output = plugin .scaffold_docker(ScaffoldDockerInput { - input_dir: VirtualPath::Real(sandbox.path().join("in")), - output_dir: VirtualPath::Real(output_dir.clone()), + input_dir: VirtualPath::new(sandbox.path().join("in")), + output_dir: VirtualPath::new(output_dir.clone()), phase: ScaffoldDockerPhase::Configs, project: Some(ProjectFragment::default()), ..Default::default() @@ -66,8 +65,8 @@ mod rust_toolchain_tier1 { assert_eq!( output.copied_files, [ - PathBuf::from("/workspace/out/src/lib.rs"), - PathBuf::from("/workspace/out/src/main.rs") + VirtualPath::new("/workspace/out/src/lib.rs"), + VirtualPath::new("/workspace/out/src/main.rs") ] ); } @@ -83,8 +82,8 @@ mod rust_toolchain_tier1 { let output = plugin .scaffold_docker(ScaffoldDockerInput { - input_dir: VirtualPath::Real(sandbox.path().join("in")), - output_dir: VirtualPath::Real(output_dir.clone()), + input_dir: VirtualPath::new(sandbox.path().join("in")), + output_dir: VirtualPath::new(output_dir.clone()), phase: ScaffoldDockerPhase::Sources, ..Default::default() }) @@ -108,8 +107,8 @@ mod rust_toolchain_tier1 { plugin .scaffold_docker(ScaffoldDockerInput { - input_dir: VirtualPath::Real(sandbox.path().join("in")), - output_dir: VirtualPath::Real(output_dir.clone()), + input_dir: VirtualPath::new(sandbox.path().join("in")), + output_dir: VirtualPath::new(output_dir.clone()), phase: ScaffoldDockerPhase::Sources, project: Some(ProjectFragment::default()), ..Default::default() @@ -135,7 +134,7 @@ mod rust_toolchain_tier1 { delete_vendor_directories: true, ..Default::default() }, - root: VirtualPath::Real(sandbox.path().into()), + root: VirtualPath::new(sandbox.path()), ..Default::default() }) .await; @@ -156,7 +155,7 @@ mod rust_toolchain_tier1 { delete_vendor_directories: false, ..Default::default() }, - root: VirtualPath::Real(sandbox.path().into()), + root: VirtualPath::new(sandbox.path()), ..Default::default() }) .await; @@ -177,7 +176,7 @@ mod rust_toolchain_tier1 { delete_vendor_directories: true, ..Default::default() }, - root: VirtualPath::Real(sandbox.path().into()), + root: VirtualPath::new(sandbox.path()), ..Default::default() }) .await; @@ -185,7 +184,10 @@ mod rust_toolchain_tier1 { assert!(!sandbox.path().join("target").exists()); assert!(!sandbox.path().join("target/other-file").exists()); - assert_eq!(output.changed_files, [PathBuf::from("/workspace/target")]); + assert_eq!( + output.changed_files, + [VirtualPath::new("/workspace/target")] + ); } #[tokio::test(flavor = "multi_thread")] @@ -199,7 +201,7 @@ mod rust_toolchain_tier1 { delete_vendor_directories: true, ..Default::default() }, - root: VirtualPath::Real(sandbox.path().into()), + root: VirtualPath::new(sandbox.path()), ..Default::default() }) .await; @@ -218,7 +220,10 @@ mod rust_toolchain_tier1 { .exists() ); - assert_eq!(output.changed_files, [PathBuf::from("/workspace/target")]); + assert_eq!( + output.changed_files, + [VirtualPath::new("/workspace/target")] + ); } } } diff --git a/toolchains/rust/tests/tier2_env_test.rs b/toolchains/rust/tests/tier2_env_test.rs index 32a047ef..877150d2 100644 --- a/toolchains/rust/tests/tier2_env_test.rs +++ b/toolchains/rust/tests/tier2_env_test.rs @@ -2,7 +2,6 @@ use moon_pdk_api::*; use moon_pdk_test_utils::{create_empty_moon_sandbox, create_moon_sandbox}; use serde_json::json; use std::fs; -use std::path::PathBuf; mod rust_toolchain_tier2 { use super::*; @@ -17,7 +16,7 @@ mod rust_toolchain_tier2 { let output = plugin .setup_environment(SetupEnvironmentInput { - root: VirtualPath::Real(sandbox.path().into()), + root: VirtualPath::new(sandbox.path()), toolchain_config: json!({ "addMsrvConstraint": true, "version": null @@ -42,7 +41,7 @@ mod rust_toolchain_tier2 { let output = plugin .setup_environment(SetupEnvironmentInput { - root: VirtualPath::Real(sandbox.path().into()), + root: VirtualPath::new(sandbox.path()), toolchain_config: json!({ "addMsrvConstraint": false, "version": "1.69.0" @@ -67,7 +66,7 @@ mod rust_toolchain_tier2 { let output = plugin .setup_environment(SetupEnvironmentInput { - root: VirtualPath::Real(sandbox.path().into()), + root: VirtualPath::new(sandbox.path()), toolchain_config: json!({ "addMsrvConstraint": true, "version": "1.69.0" @@ -84,7 +83,7 @@ mod rust_toolchain_tier2 { ); assert_eq!( output.changed_files, - [PathBuf::from("/workspace/Cargo.toml")] + [VirtualPath::new("/workspace/Cargo.toml")] ); assert!( fs::read_to_string(sandbox.path().join("Cargo.toml")) @@ -104,7 +103,7 @@ mod rust_toolchain_tier2 { let output = plugin .setup_environment(SetupEnvironmentInput { - root: VirtualPath::Real(sandbox.path().into()), + root: VirtualPath::new(sandbox.path()), toolchain_config: json!({ "syncToolchainConfig": true, "version": null @@ -129,7 +128,7 @@ mod rust_toolchain_tier2 { let output = plugin .setup_environment(SetupEnvironmentInput { - root: VirtualPath::Real(sandbox.path().into()), + root: VirtualPath::new(sandbox.path()), toolchain_config: json!({ "syncToolchainConfig": false, "version": "1.69.0" @@ -154,7 +153,7 @@ mod rust_toolchain_tier2 { let output = plugin .setup_environment(SetupEnvironmentInput { - root: VirtualPath::Real(sandbox.path().into()), + root: VirtualPath::new(sandbox.path()), toolchain_config: json!({ "syncToolchainConfig": true, "version": "1.69.0" @@ -171,7 +170,7 @@ mod rust_toolchain_tier2 { ); assert_eq!( output.changed_files, - [PathBuf::from("/workspace/rust-toolchain.toml")] + [VirtualPath::new("/workspace/rust-toolchain.toml")] ); assert!( fs::read_to_string(sandbox.path().join("rust-toolchain.toml")) @@ -189,7 +188,7 @@ mod rust_toolchain_tier2 { let output = plugin .setup_environment(SetupEnvironmentInput { - root: VirtualPath::Real(sandbox.path().into()), + root: VirtualPath::new(sandbox.path()), toolchain_config: json!({ "syncToolchainConfig": true, "version": null @@ -201,8 +200,8 @@ mod rust_toolchain_tier2 { assert_eq!( output.changed_files, [ - PathBuf::from("/workspace/rust-toolchain.toml"), - PathBuf::from("/workspace/rust-toolchain") + VirtualPath::new("/workspace/rust-toolchain.toml"), + VirtualPath::new("/workspace/rust-toolchain") ] ); assert!( @@ -222,7 +221,7 @@ mod rust_toolchain_tier2 { let output = plugin .setup_environment(SetupEnvironmentInput { - root: VirtualPath::Real(sandbox.path().into()), + root: VirtualPath::new(sandbox.path()), toolchain_config: json!({ "syncToolchainConfig": true, "version": null @@ -234,8 +233,8 @@ mod rust_toolchain_tier2 { assert_eq!( output.changed_files, [ - PathBuf::from("/workspace/rust-toolchain.toml"), - PathBuf::from("/workspace/rust-toolchain") + VirtualPath::new("/workspace/rust-toolchain.toml"), + VirtualPath::new("/workspace/rust-toolchain") ] ); assert!( @@ -257,7 +256,7 @@ mod rust_toolchain_tier2 { let output = plugin .setup_environment(SetupEnvironmentInput { - root: VirtualPath::Real(sandbox.path().into()), + root: VirtualPath::new(sandbox.path()), toolchain_config: json!({ "components": [] }), @@ -275,7 +274,7 @@ mod rust_toolchain_tier2 { let output = plugin .setup_environment(SetupEnvironmentInput { - root: VirtualPath::Real(sandbox.path().into()), + root: VirtualPath::new(sandbox.path()), toolchain_config: json!({ "components": ["rustfmt", "clippy"] }), @@ -289,7 +288,8 @@ mod rust_toolchain_tier2 { ExecCommandInput::new("rustup", ["component", "add", "rustfmt", "clippy"]) .cwd(plugin.plugin.to_virtual_path(sandbox.path())) ) - .cache("rustup-component-add")] + .cache(CacheStrategy::Memory) + .label("rustup-component-add")] ); } } @@ -304,7 +304,7 @@ mod rust_toolchain_tier2 { let output = plugin .setup_environment(SetupEnvironmentInput { - root: VirtualPath::Real(sandbox.path().into()), + root: VirtualPath::new(sandbox.path()), toolchain_config: json!({ "targets": [] }), @@ -322,7 +322,7 @@ mod rust_toolchain_tier2 { let output = plugin .setup_environment(SetupEnvironmentInput { - root: VirtualPath::Real(sandbox.path().into()), + root: VirtualPath::new(sandbox.path()), toolchain_config: json!({ "targets": ["wasm32-wasi", "nightly"] }), @@ -336,7 +336,8 @@ mod rust_toolchain_tier2 { ExecCommandInput::new("rustup", ["target", "add", "wasm32-wasi", "nightly"],) .cwd(plugin.plugin.to_virtual_path(sandbox.path())) ) - .cache("rustup-target-add")] + .cache(CacheStrategy::Memory) + .label("rustup-target-add")] ); } } @@ -351,7 +352,7 @@ mod rust_toolchain_tier2 { let output = plugin .setup_environment(SetupEnvironmentInput { - root: VirtualPath::Real(sandbox.path().into()), + root: VirtualPath::new(sandbox.path()), toolchain_config: json!({ "bins": [] }), @@ -369,7 +370,7 @@ mod rust_toolchain_tier2 { let output = plugin .setup_environment(SetupEnvironmentInput { - root: VirtualPath::Real(sandbox.path().into()), + root: VirtualPath::new(sandbox.path()), toolchain_config: json!({ "bins": [ "cargo-nextest", @@ -392,7 +393,8 @@ mod rust_toolchain_tier2 { ) .cwd(plugin.plugin.to_virtual_path(sandbox.path())) ) - .cache("cargo-binstall"), + .cache(CacheStrategy::Memory) + .label("cargo-binstall"), ExecCommand::new( ExecCommandInput::new( "cargo", @@ -407,7 +409,8 @@ mod rust_toolchain_tier2 { ) .cwd(plugin.plugin.to_virtual_path(sandbox.path())) ) - .cache("cargo-bins") + .cache(CacheStrategy::Memory) + .label("cargo-bins") ] ); } @@ -419,7 +422,7 @@ mod rust_toolchain_tier2 { let output = plugin .setup_environment(SetupEnvironmentInput { - root: VirtualPath::Real(sandbox.path().into()), + root: VirtualPath::new(sandbox.path()), toolchain_config: json!({ "bins": [ "cargo-nextest", @@ -443,7 +446,8 @@ mod rust_toolchain_tier2 { ) .cwd(plugin.plugin.to_virtual_path(sandbox.path())) ) - .cache("cargo-binstall"), + .cache(CacheStrategy::Memory) + .label("cargo-binstall"), ExecCommand::new( ExecCommandInput::new( "cargo", @@ -458,7 +462,8 @@ mod rust_toolchain_tier2 { ) .cwd(plugin.plugin.to_virtual_path(sandbox.path())) ) - .cache("cargo-bins-forced"), + .cache(CacheStrategy::Memory) + .label("cargo-bins-forced"), ExecCommand::new( ExecCommandInput::new( "cargo", @@ -472,7 +477,8 @@ mod rust_toolchain_tier2 { ) .cwd(plugin.plugin.to_virtual_path(sandbox.path())) ) - .cache("cargo-bins") + .cache(CacheStrategy::Memory) + .label("cargo-bins") ] ); } @@ -491,7 +497,7 @@ mod rust_toolchain_tier2 { let output = plugin .setup_environment(SetupEnvironmentInput { - root: VirtualPath::Real(sandbox.path().into()), + root: VirtualPath::new(sandbox.path()), toolchain_config: json!({ "bins": [ { @@ -515,7 +521,7 @@ mod rust_toolchain_tier2 { let output = plugin .setup_environment(SetupEnvironmentInput { - root: VirtualPath::Real(sandbox.path().into()), + root: VirtualPath::new(sandbox.path()), toolchain_config: json!({ "binstallVersion": "1.2.3", "bins": ["cargo-nextest"] @@ -534,7 +540,8 @@ mod rust_toolchain_tier2 { ) .cwd(plugin.plugin.to_virtual_path(sandbox.path())) ) - .cache("cargo-binstall"), + .cache(CacheStrategy::Memory) + .label("cargo-binstall"), ExecCommand::new( ExecCommandInput::new( "cargo", @@ -548,7 +555,8 @@ mod rust_toolchain_tier2 { ) .cwd(plugin.plugin.to_virtual_path(sandbox.path())) ) - .cache("cargo-bins") + .cache(CacheStrategy::Memory) + .label("cargo-bins") ] ); } @@ -563,8 +571,8 @@ mod rust_toolchain_tier2 { let output = plugin .setup_environment(SetupEnvironmentInput { - root: VirtualPath::Real(sandbox.path().into()), - globals_dir: Some(VirtualPath::Real(sandbox.path().join(".cargo-bins"))), + root: VirtualPath::new(sandbox.path()), + globals_dir: Some(VirtualPath::new(sandbox.path().join(".cargo-bins"))), toolchain_config: json!({ "bins": ["cargo-nextest"] }), @@ -587,7 +595,8 @@ mod rust_toolchain_tier2 { ) .cwd(plugin.plugin.to_virtual_path(sandbox.path())) ) - .cache("cargo-bins")] + .cache(CacheStrategy::Memory) + .label("cargo-bins")] ); } } diff --git a/toolchains/rust/tests/tier2_test.rs b/toolchains/rust/tests/tier2_test.rs index 888126e6..6490731d 100644 --- a/toolchains/rust/tests/tier2_test.rs +++ b/toolchains/rust/tests/tier2_test.rs @@ -4,7 +4,6 @@ use moon_pdk_api::*; use moon_pdk_test_utils::{create_empty_moon_sandbox, create_moon_sandbox}; use std::collections::BTreeMap; use std::env; -use std::path::PathBuf; mod rust_toolchain_tier2 { use super::*; @@ -66,9 +65,9 @@ mod rust_toolchain_tier2 { assert_eq!( output.input_files, [ - PathBuf::from("/workspace/a/Cargo.toml"), - PathBuf::from("/workspace/b/Cargo.toml"), - PathBuf::from("/workspace/c/Cargo.toml"), + VirtualPath::new("/workspace/a/Cargo.toml"), + VirtualPath::new("/workspace/b/Cargo.toml"), + VirtualPath::new("/workspace/c/Cargo.toml"), ] ); } @@ -96,7 +95,7 @@ mod rust_toolchain_tier2 { assert_eq!( output.input_files, - [PathBuf::from("/workspace/a/Cargo.toml")] + [VirtualPath::new("/workspace/a/Cargo.toml")] ); } @@ -159,7 +158,7 @@ mod rust_toolchain_tier2 { .extend_task_command(ExtendTaskCommandInput { command: "nextest".into(), args: vec!["run".into()], - globals_dir: Some(VirtualPath::Real(sandbox.path().join(".home/.cargo/bin"))), + globals_dir: Some(VirtualPath::new(sandbox.path().join(".home/.cargo/bin"))), ..Default::default() }) .await; @@ -174,7 +173,7 @@ mod rust_toolchain_tier2 { .extend_task_command(ExtendTaskCommandInput { command: "cargo-nextest".into(), args: vec!["run".into()], - globals_dir: Some(VirtualPath::Real(sandbox.path().join(".home/.cargo/bin"))), + globals_dir: Some(VirtualPath::new(sandbox.path().join(".home/.cargo/bin"))), ..Default::default() }) .await; @@ -198,7 +197,7 @@ mod rust_toolchain_tier2 { .extend_task_command(ExtendTaskCommandInput { command: "nextest".into(), args: vec!["run".into()], - globals_dir: Some(VirtualPath::Real(sandbox.path().join(".home/.cargo/bin"))), + globals_dir: Some(VirtualPath::new(sandbox.path().join(".home/.cargo/bin"))), ..Default::default() }) .await; @@ -219,7 +218,7 @@ mod rust_toolchain_tier2 { .extend_task_command(ExtendTaskCommandInput { command: "cargo".into(), args: vec!["build".into()], - globals_dir: Some(VirtualPath::Real(sandbox.path().join(".home/.cargo/bin"))), + globals_dir: Some(VirtualPath::new(sandbox.path().join(".home/.cargo/bin"))), ..Default::default() }) .await; @@ -230,7 +229,7 @@ mod rust_toolchain_tier2 { let output = plugin .extend_task_command(ExtendTaskCommandInput { command: "rustc".into(), - globals_dir: Some(VirtualPath::Real(sandbox.path().join(".home/.cargo/bin"))), + globals_dir: Some(VirtualPath::new(sandbox.path().join(".home/.cargo/bin"))), ..Default::default() }) .await; @@ -279,7 +278,7 @@ mod rust_toolchain_tier2 { let output = plugin .locate_dependencies_root(LocateDependenciesRootInput { - starting_dir: VirtualPath::Real(sandbox.path().into()), + starting_dir: VirtualPath::new(sandbox.path()), ..Default::default() }) .await; @@ -295,13 +294,13 @@ mod rust_toolchain_tier2 { let output = plugin .locate_dependencies_root(LocateDependenciesRootInput { - starting_dir: VirtualPath::Real(sandbox.path().join("package/nested")), + starting_dir: VirtualPath::new(sandbox.path().join("package/nested")), ..Default::default() }) .await; assert!(output.members.is_none()); - assert_eq!(output.root.unwrap(), PathBuf::from("/workspace/package")); + assert_eq!(output.root.unwrap(), VirtualPath::new("/workspace/package")); } #[tokio::test(flavor = "multi_thread")] @@ -311,9 +310,7 @@ mod rust_toolchain_tier2 { let output = plugin .locate_dependencies_root(LocateDependenciesRootInput { - starting_dir: VirtualPath::Real( - sandbox.path().join("package-with-lock/nested"), - ), + starting_dir: VirtualPath::new(sandbox.path().join("package-with-lock/nested")), ..Default::default() }) .await; @@ -321,7 +318,7 @@ mod rust_toolchain_tier2 { assert!(output.members.is_none()); assert_eq!( output.root.unwrap(), - PathBuf::from("/workspace/package-with-lock") + VirtualPath::new("/workspace/package-with-lock") ); } @@ -332,7 +329,7 @@ mod rust_toolchain_tier2 { let output = plugin .locate_dependencies_root(LocateDependenciesRootInput { - starting_dir: VirtualPath::Real( + starting_dir: VirtualPath::new( sandbox.path().join("workspace/crates/a/nested"), ), ..Default::default() @@ -340,7 +337,10 @@ mod rust_toolchain_tier2 { .await; assert_eq!(output.members.unwrap(), ["crates/*"]); - assert_eq!(output.root.unwrap(), PathBuf::from("/workspace/workspace")); + assert_eq!( + output.root.unwrap(), + VirtualPath::new("/workspace/workspace") + ); } #[tokio::test(flavor = "multi_thread")] @@ -350,7 +350,7 @@ mod rust_toolchain_tier2 { let output = plugin .locate_dependencies_root(LocateDependenciesRootInput { - starting_dir: VirtualPath::Real( + starting_dir: VirtualPath::new( sandbox.path().join("workspace-with-lock/crates/a/nested"), ), ..Default::default() @@ -360,7 +360,7 @@ mod rust_toolchain_tier2 { assert_eq!(output.members.unwrap(), ["crates/*"]); assert_eq!( output.root.unwrap(), - PathBuf::from("/workspace/workspace-with-lock") + VirtualPath::new("/workspace/workspace-with-lock") ); } } @@ -375,7 +375,7 @@ mod rust_toolchain_tier2 { let output = plugin .install_dependencies(InstallDependenciesInput { - root: VirtualPath::Real(sandbox.path().into()), + root: VirtualPath::new(sandbox.path()), ..Default::default() }) .await; @@ -391,7 +391,7 @@ mod rust_toolchain_tier2 { let output = plugin .install_dependencies(InstallDependenciesInput { - root: VirtualPath::Real(sandbox.path().into()), + root: VirtualPath::new(sandbox.path()), ..Default::default() }) .await; @@ -417,7 +417,7 @@ mod rust_toolchain_tier2 { let output = plugin .parse_lock(ParseLockInput { - path: VirtualPath::Real(sandbox.path().join("Cargo.lock")), + path: VirtualPath::new(sandbox.path().join("Cargo.lock")), ..Default::default() }) .await; @@ -468,7 +468,7 @@ mod rust_toolchain_tier2 { let output = plugin .parse_manifest(ParseManifestInput { - path: VirtualPath::Real(sandbox.path().join("Cargo.toml")), + path: VirtualPath::new(sandbox.path().join("Cargo.toml")), ..Default::default() }) .await; @@ -512,7 +512,7 @@ mod rust_toolchain_tier2 { let output = plugin .parse_manifest(ParseManifestInput { - path: VirtualPath::Real(sandbox.path().join("package/Cargo.toml")), + path: VirtualPath::new(sandbox.path().join("package/Cargo.toml")), ..Default::default() }) .await; diff --git a/toolchains/rust/tests/toolchain_toml_test.rs b/toolchains/rust/tests/toolchain_toml_test.rs index 4c97dca6..63c5864a 100644 --- a/toolchains/rust/tests/toolchain_toml_test.rs +++ b/toolchains/rust/tests/toolchain_toml_test.rs @@ -12,7 +12,7 @@ mod toolchain_toml { #[test] fn adds_if_not_set() { let mut tc = ToolchainToml { - path: VirtualPath::Real(PathBuf::from("/base/rust-toolchain.toml")), + path: VirtualPath::new(PathBuf::from("/base/rust-toolchain.toml")), ..Default::default() }; @@ -27,7 +27,7 @@ mod toolchain_toml { #[test] fn doesnt_add_if_empty() { let mut tc = ToolchainToml { - path: VirtualPath::Real(PathBuf::from("/base/rust-toolchain.toml")), + path: VirtualPath::new(PathBuf::from("/base/rust-toolchain.toml")), ..Default::default() }; @@ -42,7 +42,7 @@ mod toolchain_toml { #[test] fn doesnt_add_if_same_value() { let mut tc = ToolchainToml { - path: VirtualPath::Real(PathBuf::from("/base/rust-toolchain.toml")), + path: VirtualPath::new(PathBuf::from("/base/rust-toolchain.toml")), data: BaseToolchainToml { toolchain: ToolchainSection { channel: Some("stable".into()), @@ -62,7 +62,7 @@ mod toolchain_toml { #[test] fn saves_field_if_set() { let mut tc = ToolchainToml { - path: VirtualPath::Real(PathBuf::from("/base/rust-toolchain.toml")), + path: VirtualPath::new(PathBuf::from("/base/rust-toolchain.toml")), ..Default::default() }; @@ -81,7 +81,7 @@ mod toolchain_toml { #[test] fn doesnt_save_field_if_not_set() { let tc = ToolchainToml { - path: VirtualPath::Real(PathBuf::from("/base/rust-toolchain.toml")), + path: VirtualPath::new(PathBuf::from("/base/rust-toolchain.toml")), ..Default::default() }; diff --git a/toolchains/system/CHANGELOG.md b/toolchains/system/CHANGELOG.md index 0ccd8b25..3a5bf8a7 100644 --- a/toolchains/system/CHANGELOG.md +++ b/toolchains/system/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## Unreleased + +#### 🚀 Updates + +- Updated to support moon v2.5 release. + ## 1.0.2 #### 🚀 Updates diff --git a/toolchains/typescript/CHANGELOG.md b/toolchains/typescript/CHANGELOG.md index 92f31fee..116e811c 100644 --- a/toolchains/typescript/CHANGELOG.md +++ b/toolchains/typescript/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## Unreleased + +#### 🚀 Updates + +- Updated to support moon v2.5 release. + ## 1.1.3 #### 🐞 Fixes diff --git a/toolchains/typescript/src/context.rs b/toolchains/typescript/src/context.rs index b58dcb32..06eaced0 100644 --- a/toolchains/typescript/src/context.rs +++ b/toolchains/typescript/src/context.rs @@ -1,8 +1,7 @@ use crate::config::TypeScriptToolchainConfig; -use moon_pdk::VirtualPath; +use moon_pdk::{AnyResult, VirtualPath, VirtualPathExt}; use moon_pdk_api::MoonContext; use moon_project::ProjectFragment; -use std::path::PathBuf; use typescript_tsconfig_json::CompilerPath; #[derive(Debug)] @@ -13,26 +12,11 @@ pub struct TypeScriptContext { pub workspace_root: VirtualPath, } -fn create_virtual_path(base: &VirtualPath, path: PathBuf) -> VirtualPath { - match base { - VirtualPath::Real(_) => VirtualPath::Real(path), - VirtualPath::Virtual { - virtual_prefix, - real_prefix, - .. - } => VirtualPath::Virtual { - path, - virtual_prefix: virtual_prefix.to_owned(), - real_prefix: real_prefix.to_owned(), - }, - } -} - pub fn create_typescript_context( base: &MoonContext, config: &TypeScriptToolchainConfig, project: &ProjectFragment, -) -> TypeScriptContext { +) -> AnyResult { let root_config = CompilerPath::resolve( base.workspace_root .join(&config.root) @@ -52,10 +36,10 @@ pub fn create_typescript_context( .to_path_buf(), ); - TypeScriptContext { - root_config: create_virtual_path(&base.workspace_root, root_config), - root_options_config: create_virtual_path(&base.workspace_root, root_options_config), - project_config: create_virtual_path(&base.workspace_root, project_config), + Ok(TypeScriptContext { + root_config: VirtualPath::create(root_config)?, + root_options_config: VirtualPath::create(root_options_config)?, + project_config: VirtualPath::create(project_config)?, workspace_root: base.workspace_root.clone(), - } + }) } diff --git a/toolchains/typescript/src/tier1.rs b/toolchains/typescript/src/tier1.rs index 13da2551..d92c9a3d 100644 --- a/toolchains/typescript/src/tier1.rs +++ b/toolchains/typescript/src/tier1.rs @@ -91,7 +91,7 @@ pub fn sync_project(Json(input): Json) -> FnResult(input.toolchain_config)?; - let context = create_typescript_context(&input.context, &config, &input.project); + let context = create_typescript_context(&input.context, &config, &input.project)?; let (op, files) = Operation::track("sync-project-references", || { sync_project_references( @@ -103,9 +103,7 @@ pub fn sync_project(Json(input): Json) -> FnResult, ) -> FnResult> { let config = parse_toolchain_config::(input.toolchain_config)?; - let context = create_typescript_context(&input.context, &config, &input.project); + let context = create_typescript_context(&input.context, &config, &input.project)?; let mut output = HashTaskContentsOutput::default(); let mut data = json::json!({}); let mut has_data = false; diff --git a/toolchains/typescript/src/tsconfig_json.rs b/toolchains/typescript/src/tsconfig_json.rs index 2275345c..22ae8ca8 100644 --- a/toolchains/typescript/src/tsconfig_json.rs +++ b/toolchains/typescript/src/tsconfig_json.rs @@ -25,7 +25,7 @@ impl TsConfigJson { pub fn load_with_extends(path: VirtualPath) -> AnyResult { let mut config = BaseTsConfigJson::default(); - for next in BaseTsConfigJson::resolve_extends_chain(path.any_path())? { + for next in BaseTsConfigJson::resolve_extends_chain(&path)? { config.extend(next.config); } @@ -123,11 +123,8 @@ impl TsConfigJson { /// Convert an absolute virtual path to a relative virtual string, /// for use within tsconfig include, exclude, and other paths. pub fn to_relative_path(&self, path: impl AsRef) -> AnyResult { - let mut rel_path = to_relative_virtual_string( - path.as_ref().any_path(), - self.path.parent().unwrap().any_path(), - ) - .map_err(|error| anyhow!("{error}"))?; + let mut rel_path = to_relative_virtual_string(path.as_ref(), self.path.parent().unwrap()) + .map_err(|error| anyhow!("{error}"))?; // This is required for TS >= v6 because `baseUrl` was removed if rel_path != "." && !rel_path.starts_with(".") { diff --git a/toolchains/typescript/tests/tsconfig_json_test.rs b/toolchains/typescript/tests/tsconfig_json_test.rs index 46e6988f..ba42b88f 100644 --- a/toolchains/typescript/tests/tsconfig_json_test.rs +++ b/toolchains/typescript/tests/tsconfig_json_test.rs @@ -19,7 +19,7 @@ mod tsconfig_json { sandbox.create_file("tsconfig.json", json); let config_path = sandbox.path().join("tsconfig.json"); - let mut tsc = TsConfigJsonContainer::load(VirtualPath::Real(config_path.clone())).unwrap(); + let mut tsc = TsConfigJsonContainer::load(VirtualPath::new(config_path.clone())).unwrap(); // Trigger dirty tsc.dirty.push("unknown".into()); @@ -154,7 +154,7 @@ mod tsconfig_json { fn parse_basic_file() { let fixture = locate_fixture("configs"); let tsc = - TsConfigJsonContainer::load(VirtualPath::Real(fixture.join("tsconfig.default.json"))) + TsConfigJsonContainer::load(VirtualPath::new(fixture.join("tsconfig.default.json"))) .unwrap(); assert_eq!( @@ -174,7 +174,7 @@ mod tsconfig_json { #[test] fn adds_if_not_set() { let mut tsc = TsConfigJsonContainer { - path: VirtualPath::Real(PathBuf::from("/base/tsconfig.json")), + path: VirtualPath::new(PathBuf::from("/base/tsconfig.json")), ..Default::default() }; @@ -182,7 +182,7 @@ mod tsconfig_json { assert!( tsc.add_project_ref( - &VirtualPath::Real(PathBuf::from("/sibling")), + &VirtualPath::new(PathBuf::from("/sibling")), "tsconfig.json" ) .unwrap() @@ -207,13 +207,13 @@ mod tsconfig_json { }]), ..Default::default() }, - path: VirtualPath::Real(PathBuf::from("/base/tsconfig.json")), + path: VirtualPath::new(PathBuf::from("/base/tsconfig.json")), ..Default::default() }; assert!( !tsc.add_project_ref( - &VirtualPath::Real(PathBuf::from("/sibling")), + &VirtualPath::new(PathBuf::from("/sibling")), "tsconfig.json" ) .unwrap() @@ -234,7 +234,7 @@ mod tsconfig_json { data: TsConfigJson { ..Default::default() }, - path: VirtualPath::Real(PathBuf::from("/base/tsconfig.json")), + path: VirtualPath::new(PathBuf::from("/base/tsconfig.json")), ..Default::default() }; @@ -242,7 +242,7 @@ mod tsconfig_json { assert!( tsc.add_project_ref( - &VirtualPath::Real(PathBuf::from("/sibling")), + &VirtualPath::new(PathBuf::from("/sibling")), "tsconfig.ref.json" ) .unwrap() @@ -264,7 +264,7 @@ mod tsconfig_json { data: TsConfigJson { ..Default::default() }, - path: VirtualPath::Real(PathBuf::from("C:\\base\\dir\\tsconfig.json")), + path: VirtualPath::new(PathBuf::from("C:\\base\\dir\\tsconfig.json")), ..Default::default() }; @@ -272,7 +272,7 @@ mod tsconfig_json { assert!( tsc.add_project_ref( - &VirtualPath::Real(PathBuf::from("C:\\base\\sibling")), + &VirtualPath::new(PathBuf::from("C:\\base\\sibling")), "tsconfig.json" ) .unwrap() @@ -297,13 +297,13 @@ mod tsconfig_json { }]), ..Default::default() }, - path: VirtualPath::Real(PathBuf::from("/base/tsconfig.json")), + path: VirtualPath::new(PathBuf::from("/base/tsconfig.json")), ..Default::default() }; assert!( tsc.add_project_ref( - &VirtualPath::Real(PathBuf::from("/brother")), + &VirtualPath::new(PathBuf::from("/brother")), "tsconfig.json" ) .unwrap() @@ -338,13 +338,13 @@ mod tsconfig_json { }]), ..Default::default() }, - path: VirtualPath::Real(PathBuf::from("/base/tsconfig.json")), + path: VirtualPath::new(PathBuf::from("/base/tsconfig.json")), ..Default::default() }; assert!( tsc.sync_project_refs( - &[VirtualPath::Real(PathBuf::from("/sibling"))], + &[VirtualPath::new(PathBuf::from("/sibling"))], "tsconfig.json" ) .unwrap() @@ -369,7 +369,7 @@ mod tsconfig_json { }]), ..Default::default() }, - path: VirtualPath::Real(PathBuf::from("/base/tsconfig.json")), + path: VirtualPath::new(PathBuf::from("/base/tsconfig.json")), ..Default::default() }; From 3ccc9f664c9d3e5e4e68fa157c390c55411b7ae6 Mon Sep 17 00:00:00 2001 From: Miles Johnson Date: Mon, 10 Aug 2026 15:16:39 -0700 Subject: [PATCH 43/78] chore: Release --- Cargo.lock | 2 +- extensions/download/CHANGELOG.md | 2 +- extensions/download/Cargo.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e8617a38..efbfab08 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1430,7 +1430,7 @@ checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" [[package]] name = "download_extension" -version = "1.0.2" +version = "1.0.3" dependencies = [ "extension_common", "extism-pdk", diff --git a/extensions/download/CHANGELOG.md b/extensions/download/CHANGELOG.md index 9e95a83e..840329b3 100644 --- a/extensions/download/CHANGELOG.md +++ b/extensions/download/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## Unreleased +## 1.0.3 #### 🚀 Updates diff --git a/extensions/download/Cargo.toml b/extensions/download/Cargo.toml index 8b0f9573..a5eaf223 100644 --- a/extensions/download/Cargo.toml +++ b/extensions/download/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "download_extension" -version = "1.0.2" +version = "1.0.3" edition = "2024" description = "moon extension WASM plugin for downloading a file from a URL." authors = ["Miles Johnson"] From 72c140faa8ce7c89dc0df250fb21f36a7fdbb82d Mon Sep 17 00:00:00 2001 From: Miles Johnson Date: Mon, 10 Aug 2026 15:16:52 -0700 Subject: [PATCH 44/78] chore: Release --- Cargo.lock | 2 +- extensions/migrate-nx/CHANGELOG.md | 2 +- extensions/migrate-nx/Cargo.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index efbfab08..84a847bb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2996,7 +2996,7 @@ dependencies = [ [[package]] name = "migrate_nx_extension" -version = "1.0.3" +version = "1.0.4" dependencies = [ "extension_common", "extism-pdk", diff --git a/extensions/migrate-nx/CHANGELOG.md b/extensions/migrate-nx/CHANGELOG.md index b3e257b4..85573e24 100644 --- a/extensions/migrate-nx/CHANGELOG.md +++ b/extensions/migrate-nx/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## Unreleased +## 1.0.4 #### 🚀 Updates diff --git a/extensions/migrate-nx/Cargo.toml b/extensions/migrate-nx/Cargo.toml index 5840a6c0..76fa1497 100644 --- a/extensions/migrate-nx/Cargo.toml +++ b/extensions/migrate-nx/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "migrate_nx_extension" -version = "1.0.3" +version = "1.0.4" edition = "2024" description = "moon extension WASM plugin for migrating an Nx repository to moon." authors = ["Miles Johnson"] From ba544e67449478e2680ff5101c5273fc9bf986bd Mon Sep 17 00:00:00 2001 From: Miles Johnson Date: Mon, 10 Aug 2026 15:17:06 -0700 Subject: [PATCH 45/78] chore: Release --- Cargo.lock | 2 +- extensions/migrate-turborepo/CHANGELOG.md | 2 +- extensions/migrate-turborepo/Cargo.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 84a847bb..a5b0b530 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3016,7 +3016,7 @@ dependencies = [ [[package]] name = "migrate_turborepo_extension" -version = "1.0.3" +version = "1.0.4" dependencies = [ "extension_common", "extism-pdk", diff --git a/extensions/migrate-turborepo/CHANGELOG.md b/extensions/migrate-turborepo/CHANGELOG.md index 47c52050..f4405345 100644 --- a/extensions/migrate-turborepo/CHANGELOG.md +++ b/extensions/migrate-turborepo/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## Unreleased +## 1.0.4 #### 🚀 Updates diff --git a/extensions/migrate-turborepo/Cargo.toml b/extensions/migrate-turborepo/Cargo.toml index 57a78626..5582b55b 100644 --- a/extensions/migrate-turborepo/Cargo.toml +++ b/extensions/migrate-turborepo/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "migrate_turborepo_extension" -version = "1.0.3" +version = "1.0.4" edition = "2024" description = "moon extension WASM plugin for migrating a Turborepo repository to moon." authors = ["Miles Johnson"] From c96b9a58c772cb02a327f604bc15dcf66a585457 Mon Sep 17 00:00:00 2001 From: Miles Johnson Date: Mon, 10 Aug 2026 15:17:19 -0700 Subject: [PATCH 46/78] chore: Release --- Cargo.lock | 2 +- extensions/unpack/CHANGELOG.md | 2 +- extensions/unpack/Cargo.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a5b0b530..021808bd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6185,7 +6185,7 @@ checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" [[package]] name = "unpack_extension" -version = "1.0.2" +version = "1.0.3" dependencies = [ "extension_common", "extism-pdk", diff --git a/extensions/unpack/CHANGELOG.md b/extensions/unpack/CHANGELOG.md index a27bc0c6..9e783337 100644 --- a/extensions/unpack/CHANGELOG.md +++ b/extensions/unpack/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## Unreleased +## 1.0.3 #### 🚀 Updates diff --git a/extensions/unpack/Cargo.toml b/extensions/unpack/Cargo.toml index 6e1621f7..1d8c6136 100644 --- a/extensions/unpack/Cargo.toml +++ b/extensions/unpack/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "unpack_extension" -version = "1.0.2" +version = "1.0.3" edition = "2024" description = "moon extension WASM plugin for unpacking archive files (tar, zip, etc.)." authors = ["Miles Johnson"] From 33ba67f516c6112a8c82a04c1e8629f2fff05ca3 Mon Sep 17 00:00:00 2001 From: Miles Johnson Date: Mon, 10 Aug 2026 15:18:01 -0700 Subject: [PATCH 47/78] chore: Release --- Cargo.lock | 2 +- toolchains/bun/CHANGELOG.md | 2 +- toolchains/bun/Cargo.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 021808bd..65f59041 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -289,7 +289,7 @@ dependencies = [ [[package]] name = "bun_toolchain" -version = "1.0.2" +version = "1.0.3" dependencies = [ "bun_tool", "extism-pdk", diff --git a/toolchains/bun/CHANGELOG.md b/toolchains/bun/CHANGELOG.md index 3131cc7d..631befe4 100644 --- a/toolchains/bun/CHANGELOG.md +++ b/toolchains/bun/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## Unreleased +## 1.0.3 #### 🚀 Updates diff --git a/toolchains/bun/Cargo.toml b/toolchains/bun/Cargo.toml index 72d42e8a..4cc958c1 100644 --- a/toolchains/bun/Cargo.toml +++ b/toolchains/bun/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "bun_toolchain" -version = "1.0.2" +version = "1.0.3" edition = "2024" description = "Bun toolchain WASM plugin for moon." authors = ["Miles Johnson"] From 3ea5cfe795f558d057a82743cc865c580f1b8719 Mon Sep 17 00:00:00 2001 From: Miles Johnson Date: Mon, 10 Aug 2026 15:18:15 -0700 Subject: [PATCH 48/78] chore: Release --- Cargo.lock | 2 +- toolchains/deno/CHANGELOG.md | 2 +- toolchains/deno/Cargo.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 65f59041..0dc905bf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1204,7 +1204,7 @@ dependencies = [ [[package]] name = "deno_toolchain" -version = "1.1.0" +version = "1.1.1" dependencies = [ "deno_tool", "extism-pdk", diff --git a/toolchains/deno/CHANGELOG.md b/toolchains/deno/CHANGELOG.md index 1b7d9c59..2417193b 100644 --- a/toolchains/deno/CHANGELOG.md +++ b/toolchains/deno/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## Unreleased +## 1.1.1 #### 🚀 Updates diff --git a/toolchains/deno/Cargo.toml b/toolchains/deno/Cargo.toml index cabcef0d..b9348b1b 100644 --- a/toolchains/deno/Cargo.toml +++ b/toolchains/deno/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "deno_toolchain" -version = "1.1.0" +version = "1.1.1" edition = "2024" description = "Deno toolchain WASM plugin for moon." authors = ["Miles Johnson"] From 5a842fe837e31bc0b67a05efce34c601b956c4cb Mon Sep 17 00:00:00 2001 From: Miles Johnson Date: Mon, 10 Aug 2026 15:18:29 -0700 Subject: [PATCH 49/78] chore: Release --- Cargo.lock | 2 +- toolchains/go/CHANGELOG.md | 2 +- toolchains/go/Cargo.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0dc905bf..ad007d20 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1984,7 +1984,7 @@ dependencies = [ [[package]] name = "go_toolchain" -version = "1.4.4" +version = "1.4.5" dependencies = [ "extism-pdk", "go_tool", diff --git a/toolchains/go/CHANGELOG.md b/toolchains/go/CHANGELOG.md index 74f16a91..b3188a68 100644 --- a/toolchains/go/CHANGELOG.md +++ b/toolchains/go/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## Unreleased +## 1.4.5 #### 🚀 Updates diff --git a/toolchains/go/Cargo.toml b/toolchains/go/Cargo.toml index 9a148fb1..3be17187 100644 --- a/toolchains/go/Cargo.toml +++ b/toolchains/go/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "go_toolchain" -version = "1.4.4" +version = "1.4.5" edition = "2024" description = "Go toolchain WASM plugin for moon." authors = ["Miles Johnson"] From 9538eaeadbd421c418fe2ee77b8227673fd8d2c3 Mon Sep 17 00:00:00 2001 From: Miles Johnson Date: Mon, 10 Aug 2026 15:18:42 -0700 Subject: [PATCH 50/78] chore: Release --- Cargo.lock | 2 +- toolchains/javascript/CHANGELOG.md | 2 +- toolchains/javascript/Cargo.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ad007d20..7eb3b2b9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2595,7 +2595,7 @@ dependencies = [ [[package]] name = "javascript_toolchain" -version = "1.2.1" +version = "1.2.2" dependencies = [ "deno_lockfile", "extism-pdk", diff --git a/toolchains/javascript/CHANGELOG.md b/toolchains/javascript/CHANGELOG.md index ddcf03af..e1728bdc 100644 --- a/toolchains/javascript/CHANGELOG.md +++ b/toolchains/javascript/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## Unreleased +## 1.2.2 #### 🚀 Updates diff --git a/toolchains/javascript/Cargo.toml b/toolchains/javascript/Cargo.toml index fa8a3528..3d269354 100644 --- a/toolchains/javascript/Cargo.toml +++ b/toolchains/javascript/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "javascript_toolchain" -version = "1.2.1" +version = "1.2.2" edition = "2024" description = "JavaScript toolchain WASM plugin for moon." authors = ["Miles Johnson"] From 5a4d0c3156699b82b1b5fe06fab2597c4133b4dd Mon Sep 17 00:00:00 2001 From: Miles Johnson Date: Mon, 10 Aug 2026 15:18:56 -0700 Subject: [PATCH 51/78] chore: Release --- Cargo.lock | 2 +- toolchains/node/CHANGELOG.md | 2 +- toolchains/node/Cargo.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7eb3b2b9..06c2f717 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3332,7 +3332,7 @@ dependencies = [ [[package]] name = "node_toolchain" -version = "1.0.2" +version = "1.0.3" dependencies = [ "extism-pdk", "moon_common", diff --git a/toolchains/node/CHANGELOG.md b/toolchains/node/CHANGELOG.md index cef8fb35..605c3be2 100644 --- a/toolchains/node/CHANGELOG.md +++ b/toolchains/node/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## Unreleased +## 1.0.3 #### 🚀 Updates diff --git a/toolchains/node/Cargo.toml b/toolchains/node/Cargo.toml index bc3aaf5a..45b1fc2c 100644 --- a/toolchains/node/Cargo.toml +++ b/toolchains/node/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "node_toolchain" -version = "1.0.2" +version = "1.0.3" edition = "2024" description = "Node.js toolchain WASM plugin for moon." authors = ["Miles Johnson"] From 732c5ec23f806092074d06d45f3b5d380e4058e6 Mon Sep 17 00:00:00 2001 From: Miles Johnson Date: Mon, 10 Aug 2026 15:19:09 -0700 Subject: [PATCH 52/78] chore: Release --- Cargo.lock | 2 +- toolchains/node-depman/CHANGELOG.md | 2 +- toolchains/node-depman/Cargo.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 06c2f717..abc21b56 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3294,7 +3294,7 @@ dependencies = [ [[package]] name = "node_depman_toolchain" -version = "1.0.3" +version = "1.0.4" dependencies = [ "extism-pdk", "moon_config", diff --git a/toolchains/node-depman/CHANGELOG.md b/toolchains/node-depman/CHANGELOG.md index 6ac73c62..d1b09134 100644 --- a/toolchains/node-depman/CHANGELOG.md +++ b/toolchains/node-depman/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## Unreleased +## 1.0.4 #### 🚀 Updates diff --git a/toolchains/node-depman/Cargo.toml b/toolchains/node-depman/Cargo.toml index 1fd70a2e..ca4cb6bc 100644 --- a/toolchains/node-depman/Cargo.toml +++ b/toolchains/node-depman/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "node_depman_toolchain" -version = "1.0.3" +version = "1.0.4" edition = "2024" description = "Node.js dependency managers (npm, pnpm, yarn) toolchain WASM plugin for moon." authors = ["Miles Johnson"] From 65c06022c12399a54adddb13ef1e5d662b6d63ee Mon Sep 17 00:00:00 2001 From: Miles Johnson Date: Mon, 10 Aug 2026 15:19:23 -0700 Subject: [PATCH 53/78] chore: Release --- Cargo.lock | 2 +- toolchains/python/CHANGELOG.md | 2 +- toolchains/python/Cargo.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index abc21b56..cefa6c5e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4144,7 +4144,7 @@ dependencies = [ [[package]] name = "python_toolchain" -version = "0.2.0" +version = "0.2.1" dependencies = [ "extism-pdk", "moon_common", diff --git a/toolchains/python/CHANGELOG.md b/toolchains/python/CHANGELOG.md index 439f9aed..8354a977 100644 --- a/toolchains/python/CHANGELOG.md +++ b/toolchains/python/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## Unreleased +## 0.2.1 #### 🚀 Updates diff --git a/toolchains/python/Cargo.toml b/toolchains/python/Cargo.toml index 6578ee10..0699c8ec 100644 --- a/toolchains/python/Cargo.toml +++ b/toolchains/python/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "python_toolchain" -version = "0.2.0" +version = "0.2.1" edition = "2024" description = "Python toolchain WASM plugin for moon." authors = ["Miles Johnson"] From 1188f2fbd55fd5a261e1ca5c6b53f2d32032d84f Mon Sep 17 00:00:00 2001 From: Miles Johnson Date: Mon, 10 Aug 2026 15:19:36 -0700 Subject: [PATCH 54/78] chore: Release --- Cargo.lock | 2 +- toolchains/python-pip/CHANGELOG.md | 2 +- toolchains/python-pip/Cargo.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index cefa6c5e..f50ce8b4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4090,7 +4090,7 @@ dependencies = [ [[package]] name = "python_pip_toolchain" -version = "0.1.2" +version = "0.1.3" dependencies = [ "extism-pdk", "moon_config", diff --git a/toolchains/python-pip/CHANGELOG.md b/toolchains/python-pip/CHANGELOG.md index e97d3b44..8407898b 100644 --- a/toolchains/python-pip/CHANGELOG.md +++ b/toolchains/python-pip/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## Unreleased +## 0.1.3 #### 🚀 Updates diff --git a/toolchains/python-pip/Cargo.toml b/toolchains/python-pip/Cargo.toml index ef12695f..50021c2d 100644 --- a/toolchains/python-pip/Cargo.toml +++ b/toolchains/python-pip/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "python_pip_toolchain" -version = "0.1.2" +version = "0.1.3" edition = "2024" description = "Python pip toolchain WASM plugin for moon." authors = ["Miles Johnson"] From 4f06718aa51cf6094e1c77d3caed427b60af76ea Mon Sep 17 00:00:00 2001 From: Miles Johnson Date: Mon, 10 Aug 2026 15:19:50 -0700 Subject: [PATCH 55/78] chore: Release --- Cargo.lock | 2 +- toolchains/python-poetry/CHANGELOG.md | 2 +- toolchains/python-poetry/Cargo.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f50ce8b4..a5fd76e0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4117,7 +4117,7 @@ dependencies = [ [[package]] name = "python_poetry_toolchain" -version = "0.1.0" +version = "0.1.1" dependencies = [ "extism-pdk", "moon_config", diff --git a/toolchains/python-poetry/CHANGELOG.md b/toolchains/python-poetry/CHANGELOG.md index e4b5f181..83e9ba1b 100644 --- a/toolchains/python-poetry/CHANGELOG.md +++ b/toolchains/python-poetry/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## Unreleased +## 0.1.1 #### 🚀 Updates diff --git a/toolchains/python-poetry/Cargo.toml b/toolchains/python-poetry/Cargo.toml index 9fffdf1b..097c3c70 100644 --- a/toolchains/python-poetry/Cargo.toml +++ b/toolchains/python-poetry/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "python_poetry_toolchain" -version = "0.1.0" +version = "0.1.1" edition = "2024" description = "Python Poetry toolchain WASM plugin for moon." authors = ["Miles Johnson"] From c6d1550345feead83d78ed62f4a4c4ea650e729c Mon Sep 17 00:00:00 2001 From: Miles Johnson Date: Mon, 10 Aug 2026 15:20:03 -0700 Subject: [PATCH 56/78] chore: Release --- Cargo.lock | 2 +- toolchains/python-uv/CHANGELOG.md | 2 +- toolchains/python-uv/Cargo.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a5fd76e0..6893f201 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4181,7 +4181,7 @@ dependencies = [ [[package]] name = "python_uv_toolchain" -version = "0.1.3" +version = "0.1.4" dependencies = [ "extism-pdk", "moon_config", diff --git a/toolchains/python-uv/CHANGELOG.md b/toolchains/python-uv/CHANGELOG.md index ee8af633..44374b21 100644 --- a/toolchains/python-uv/CHANGELOG.md +++ b/toolchains/python-uv/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## Unreleased +## 0.1.4 #### 🚀 Updates diff --git a/toolchains/python-uv/Cargo.toml b/toolchains/python-uv/Cargo.toml index 599669e3..88ce0f11 100644 --- a/toolchains/python-uv/Cargo.toml +++ b/toolchains/python-uv/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "python_uv_toolchain" -version = "0.1.3" +version = "0.1.4" edition = "2024" description = "Python uv toolchain WASM plugin for moon." authors = ["Miles Johnson"] From 334cfaf1430546decf5a04bd9e98253a1dbd4a1d Mon Sep 17 00:00:00 2001 From: Miles Johnson Date: Mon, 10 Aug 2026 15:20:19 -0700 Subject: [PATCH 57/78] chore: Release --- Cargo.lock | 2 +- toolchains/ruby/CHANGELOG.md | 2 +- toolchains/ruby/Cargo.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6893f201..747ede78 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4620,7 +4620,7 @@ dependencies = [ [[package]] name = "ruby_toolchain" -version = "0.1.0" +version = "0.1.1" dependencies = [ "extism-pdk", "moon_common", diff --git a/toolchains/ruby/CHANGELOG.md b/toolchains/ruby/CHANGELOG.md index 3ad69162..db3a27fc 100644 --- a/toolchains/ruby/CHANGELOG.md +++ b/toolchains/ruby/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## Unreleased +## 0.1.1 #### 🚀 Updates diff --git a/toolchains/ruby/Cargo.toml b/toolchains/ruby/Cargo.toml index 98621149..30f0fbdf 100644 --- a/toolchains/ruby/Cargo.toml +++ b/toolchains/ruby/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ruby_toolchain" -version = "0.1.0" +version = "0.1.1" edition = "2024" description = "Ruby toolchain WASM plugin for moon." authors = ["Alex Launi"] From 346c46ea9db280ff4e110f42695a16476bcee2b9 Mon Sep 17 00:00:00 2001 From: Miles Johnson Date: Mon, 10 Aug 2026 15:20:33 -0700 Subject: [PATCH 58/78] chore: Release --- Cargo.lock | 2 +- toolchains/rust/CHANGELOG.md | 2 +- toolchains/rust/Cargo.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 747ede78..3b0a7047 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4667,7 +4667,7 @@ dependencies = [ [[package]] name = "rust_toolchain" -version = "1.0.7" +version = "1.0.8" dependencies = [ "cargo-lock", "cargo_toml", diff --git a/toolchains/rust/CHANGELOG.md b/toolchains/rust/CHANGELOG.md index 1dc24027..84ca2f04 100644 --- a/toolchains/rust/CHANGELOG.md +++ b/toolchains/rust/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## Unreleased +## 1.0.8 #### 🚀 Updates diff --git a/toolchains/rust/Cargo.toml b/toolchains/rust/Cargo.toml index 8189be13..32025e56 100644 --- a/toolchains/rust/Cargo.toml +++ b/toolchains/rust/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rust_toolchain" -version = "1.0.7" +version = "1.0.8" edition = "2024" description = "Rust toolchain WASM plugin for moon." authors = ["Miles Johnson"] From 73d291a88b85b86facb0445272c4cabf0a06ebf9 Mon Sep 17 00:00:00 2001 From: Miles Johnson Date: Mon, 10 Aug 2026 15:20:47 -0700 Subject: [PATCH 59/78] chore: Release --- Cargo.lock | 2 +- toolchains/system/CHANGELOG.md | 2 +- toolchains/system/Cargo.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3b0a7047..7148104b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5649,7 +5649,7 @@ dependencies = [ [[package]] name = "system_toolchain" -version = "1.0.2" +version = "1.0.3" dependencies = [ "extism-pdk", "moon_pdk_api", diff --git a/toolchains/system/CHANGELOG.md b/toolchains/system/CHANGELOG.md index 3a5bf8a7..b539ea3e 100644 --- a/toolchains/system/CHANGELOG.md +++ b/toolchains/system/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## Unreleased +## 1.0.3 #### 🚀 Updates diff --git a/toolchains/system/Cargo.toml b/toolchains/system/Cargo.toml index 01093e76..b5fc352b 100644 --- a/toolchains/system/Cargo.toml +++ b/toolchains/system/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "system_toolchain" -version = "1.0.2" +version = "1.0.3" edition = "2024" description = "System toolchain WASM plugin for moon, for running tasks against system-installed binaries." authors = ["Miles Johnson"] From ffc07d2abd3cd5cb960e184426fec4cb22f5b730 Mon Sep 17 00:00:00 2001 From: Miles Johnson Date: Mon, 10 Aug 2026 15:20:59 -0700 Subject: [PATCH 60/78] chore: Release --- Cargo.lock | 2 +- toolchains/typescript/CHANGELOG.md | 2 +- toolchains/typescript/Cargo.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7148104b..71c7ce1d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6098,7 +6098,7 @@ checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" [[package]] name = "typescript_toolchain" -version = "1.1.3" +version = "1.1.4" dependencies = [ "extism-pdk", "moon_common", diff --git a/toolchains/typescript/CHANGELOG.md b/toolchains/typescript/CHANGELOG.md index 116e811c..f4897e31 100644 --- a/toolchains/typescript/CHANGELOG.md +++ b/toolchains/typescript/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## Unreleased +## 1.1.4 #### 🚀 Updates diff --git a/toolchains/typescript/Cargo.toml b/toolchains/typescript/Cargo.toml index 9c565ad3..ad332d1b 100644 --- a/toolchains/typescript/Cargo.toml +++ b/toolchains/typescript/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "typescript_toolchain" -version = "1.1.3" +version = "1.1.4" edition = "2024" description = "TypeScript toolchain WASM plugin for moon." authors = ["Miles Johnson"] From b8edc6db7a009e3712b99866352b07cb68a9eda2 Mon Sep 17 00:00:00 2001 From: Miles Johnson Date: Tue, 11 Aug 2026 12:04:31 -0700 Subject: [PATCH 61/78] new: Add unstable Nub toolchain support. (#180) --- toolchains/javascript/CHANGELOG.md | 11 + toolchains/javascript/src/config.rs | 15 +- toolchains/javascript/src/infer_tasks.rs | 5 +- toolchains/javascript/src/tier1.rs | 12 + toolchains/javascript/src/tier2.rs | 68 +++- .../tests/__fixtures__/lockfiles/nub/nub.lock | 84 +++++ toolchains/javascript/tests/tier1_test.rs | 34 ++ toolchains/javascript/tests/tier2_env_test.rs | 32 ++ toolchains/javascript/tests/tier2_test.rs | 320 ++++++++++++++++++ toolchains/node-depman/CHANGELOG.md | 6 + toolchains/node-depman/Cargo.toml | 2 +- toolchains/node-depman/src/config.rs | 14 + toolchains/node-depman/src/tier1.rs | 9 +- toolchains/node-depman/src/tier2.rs | 26 +- toolchains/node-depman/tests/tier1_test.rs | 17 + toolchains/node-depman/tests/tier2_test.rs | 16 + 16 files changed, 646 insertions(+), 25 deletions(-) create mode 100644 toolchains/javascript/tests/__fixtures__/lockfiles/nub/nub.lock diff --git a/toolchains/javascript/CHANGELOG.md b/toolchains/javascript/CHANGELOG.md index e1728bdc..1782fa99 100644 --- a/toolchains/javascript/CHANGELOG.md +++ b/toolchains/javascript/CHANGELOG.md @@ -1,5 +1,16 @@ # Changelog +## Unreleased + +#### 🚀 Updates + +- Added unstable support for [Nub](https://nubjs.com/) as a package manager: + - Natively uses `nub.lock` (pnpm lockfile format), but will locate dependency + roots using other package manager lockfiles that nub can operate on. + - Reads workspace members and catalogs from `pnpm-workspace.yaml` when + present, otherwise from `package.json`. + - Does not require the Node.js toolchain, as nub is a standalone binary. + ## 1.2.2 #### 🚀 Updates diff --git a/toolchains/javascript/src/config.rs b/toolchains/javascript/src/config.rs index 6a42cf08..d389f6bb 100644 --- a/toolchains/javascript/src/config.rs +++ b/toolchains/javascript/src/config.rs @@ -12,6 +12,7 @@ derive_enum!( Deno, #[default] Npm, + Nub, Pnpm, Yarn, } @@ -20,14 +21,22 @@ derive_enum!( impl JavaScriptPackageManager { pub fn get_runtime_toolchain(&self) -> Id { match self { - JavaScriptPackageManager::Bun => Id::raw("bun"), - JavaScriptPackageManager::Deno => Id::raw("deno"), + Self::Bun => Id::raw("bun"), + Self::Deno => Id::raw("deno"), + Self::Nub => Id::raw("nub"), _ => Id::raw("node"), } } + /// Installs dependencies for the Node.js ecosystem. pub fn is_for_node(&self) -> bool { - matches!(self, Self::Npm | Self::Pnpm | Self::Yarn) + matches!(self, Self::Npm | Self::Nub | Self::Pnpm | Self::Yarn) + } + + /// Runs as a standalone binary and does not require the + /// Node.js toolchain to operate. + pub fn is_standalone(&self) -> bool { + matches!(self, Self::Bun | Self::Deno | Self::Nub) } } diff --git a/toolchains/javascript/src/infer_tasks.rs b/toolchains/javascript/src/infer_tasks.rs index 605c5ce2..c706a237 100644 --- a/toolchains/javascript/src/infer_tasks.rs +++ b/toolchains/javascript/src/infer_tasks.rs @@ -139,10 +139,7 @@ impl<'a> TasksInferrer<'a> { } // toolchains - if matches!( - package_manager, - JavaScriptPackageManager::Bun | JavaScriptPackageManager::Deno - ) { + if package_manager.is_standalone() { config.toolchains = Some(OneOrMany::Many(vec![ Id::raw("javascript"), package_manager.get_runtime_toolchain(), diff --git a/toolchains/javascript/src/tier1.rs b/toolchains/javascript/src/tier1.rs index 1f4d26ab..59a9ca4c 100644 --- a/toolchains/javascript/src/tier1.rs +++ b/toolchains/javascript/src/tier1.rs @@ -42,6 +42,8 @@ pub fn register_toolchain( "deno.jsonc".into(), // npm ".npmrc".into(), + // nub + "nub.jsonc".into(), // pnpm "pnpm-workspace.yaml".into(), ".pnpmfile.*".into(), @@ -60,6 +62,8 @@ pub fn register_toolchain( // npm "package-lock.json".into(), "npm-shrinkwrap.json".into(), + // nub + "nub.lock".into(), // pnpm "pnpm-lock.yaml".into(), // yarn @@ -78,9 +82,14 @@ pub fn register_toolchain( // npm "npm".into(), "npx".into(), + // nub + "nub".into(), + "nubx".into(), // pnpm "pnpm".into(), "pnpx".into(), + "pn".into(), + "pnx".into(), // yarn "yarn".into(), "yarnpkg".into() @@ -114,6 +123,7 @@ pub fn initialize_toolchain( JsonValue::String("bun".into()), JsonValue::String("deno".into()), JsonValue::String("npm".into()), + JsonValue::String("nub".into()), JsonValue::String("pnpm".into()), JsonValue::String("yarn".into()), ], @@ -157,6 +167,8 @@ fn detect_package_manager(root: &VirtualPath) -> AnyResult String { format!("{prefix}:{}", root.to_string().trim_end_matches('/')) } @@ -253,7 +265,9 @@ fn extract_workspace_members_and_catalogs( catalogs = deno.extract_catalogs(); members = deno.workspace.map(|ws| ws.get_members().to_vec()); } - JavaScriptPackageManager::Pnpm => { + // Nub reads `pnpm-workspace.yaml` when operating on a pnpm + // workspace, otherwise `package.json` (handled below) + JavaScriptPackageManager::Nub | JavaScriptPackageManager::Pnpm => { let workspace_file = root.join("pnpm-workspace.yaml"); if workspace_file.exists() { @@ -322,7 +336,9 @@ pub fn locate_dependencies_root( let workspace_manifest_names = match package_manager { JavaScriptPackageManager::Deno => vec!["deno.json", "deno.jsonc", "package.json"], - JavaScriptPackageManager::Pnpm => vec!["pnpm-workspace.yaml", "package.json"], + JavaScriptPackageManager::Nub | JavaScriptPackageManager::Pnpm => { + vec!["pnpm-workspace.yaml", "package.json"] + } _ => vec!["package.json"], }; @@ -345,6 +361,7 @@ pub fn locate_dependencies_root( } } JavaScriptPackageManager::Npm => vec!["package-lock.json", "npm-shrinkwrap.json"], + JavaScriptPackageManager::Nub => NUB_LOCK_NAMES.to_vec(), JavaScriptPackageManager::Pnpm => vec!["pnpm-lock.yaml"], JavaScriptPackageManager::Yarn => vec!["yarn.lock"], }; @@ -469,6 +486,39 @@ pub fn install_dependencies( cmd } + JavaScriptPackageManager::Nub => { + // `nub ci` installs strictly from the lockfile, but has no + // `--prod` flag, so fall back to `nub install` for production + // installs, which is frozen by default in CI anyways + let use_ci = env.ci + && !input.production + && NUB_LOCK_NAMES + .iter() + .any(|name| input.root.join(name).exists()); + + let mut cmd = if use_ci { + ExecCommandInput::new("nub", ["ci"]) + } else { + ExecCommandInput::new("nub", ["install"]) + }; + + if input.production { + cmd.args.push("--prod".into()); + } + + for package_name in input.packages { + cmd.args.push(if input.production { + "--filter-prod".into() + } else { + "--filter".into() + }); + + // https://nubjs.com/docs/install#nub-install + cmd.args.push(format!("{package_name}...")); + } + + cmd + } JavaScriptPackageManager::Pnpm => { let mut cmd = ExecCommandInput::new("pnpm", ["install"]); @@ -530,6 +580,13 @@ pub fn install_dependencies( .into(), ); } + JavaScriptPackageManager::Nub => { + output.dedupe_command = Some( + ExecCommandInput::new("nub", ["dedupe"]) + .cwd(input.root) + .into(), + ); + } JavaScriptPackageManager::Pnpm => { output.dedupe_command = Some(if package_manager_config.version_satisfies("<7.26.0") { @@ -590,7 +647,10 @@ pub fn parse_lock(Json(input): Json) -> FnResult { parse_package_lock_json(&input.path, &mut output)? } - Some("pnpm-lock.yaml") => parse_pnpm_lock_yaml(&input.path, &mut output)?, + // `nub.lock` uses the pnpm lockfile format + Some("nub.lock") | Some("pnpm-lock.yaml") => { + parse_pnpm_lock_yaml(&input.path, &mut output)? + } Some("yarn.lock") => parse_yarn_lock(&input.path, &mut output)?, _ => {} }; diff --git a/toolchains/javascript/tests/__fixtures__/lockfiles/nub/nub.lock b/toolchains/javascript/tests/__fixtures__/lockfiles/nub/nub.lock new file mode 100644 index 00000000..52fd1b9e --- /dev/null +++ b/toolchains/javascript/tests/__fixtures__/lockfiles/nub/nub.lock @@ -0,0 +1,84 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + devDependencies: + typescript: + specifier: ^5.9.0 + version: 5.9.2 + + a: + dependencies: + react: + specifier: ^19.1.0 + version: 19.1.1 + + b: + dependencies: + react: + specifier: ^19.1.0 + version: 19.1.1 + solid-js: + specifier: ~1.9.9 + version: 1.9.9 + + c: + dependencies: + a: + specifier: file:../a + version: link:../a + solid-js: + specifier: ~1.9.9 + version: 1.9.9 + +packages: + + csstype@3.1.3: + resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} + + react@19.1.1: + resolution: {integrity: sha512-w8nqGImo45dmMIfljjMwOGtbmC/mk4CMYhWIicdSflH91J9TyCyczcPFXJzrZ/ZXcgGRFeP6BU0BEJTw6tZdfQ==} + engines: {node: '>=0.10.0'} + + seroval-plugins@1.3.2: + resolution: {integrity: sha512-0QvCV2lM3aj/U3YozDiVwx9zpH0q8A60CTWIv4Jszj/givcudPb48B+rkU5D51NJ0pTpweGMttHjboPa9/zoIQ==} + engines: {node: '>=10'} + peerDependencies: + seroval: ^1.0 + + seroval@1.3.2: + resolution: {integrity: sha512-RbcPH1n5cfwKrru7v7+zrZvjLurgHhGyso3HTyGtRivGWgYjbOmGuivCQaORNELjNONoK35nj28EoWul9sb1zQ==} + engines: {node: '>=10'} + + solid-js@1.9.9: + resolution: {integrity: sha512-A0ZBPJQldAeGCTW0YRYJmt7RCeh5rbFfPZ2aOttgYnctHE7HgKeHCBB/PVc2P7eOfmNXqMFFFoYYdm3S4dcbkA==} + + typescript@5.9.2: + resolution: {integrity: sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==} + engines: {node: '>=14.17'} + hasBin: true + +snapshots: + + csstype@3.1.3: {} + + react@19.1.1: {} + + seroval-plugins@1.3.2(seroval@1.3.2): + dependencies: + seroval: 1.3.2 + + seroval@1.3.2: {} + + solid-js@1.9.9: + dependencies: + csstype: 3.1.3 + seroval: 1.3.2 + seroval-plugins: 1.3.2(seroval@1.3.2) + + typescript@5.9.2: {} diff --git a/toolchains/javascript/tests/tier1_test.rs b/toolchains/javascript/tests/tier1_test.rs index b18e7e4d..5117ab9c 100644 --- a/toolchains/javascript/tests/tier1_test.rs +++ b/toolchains/javascript/tests/tier1_test.rs @@ -111,6 +111,40 @@ mod javascript_toolchain_tier1 { ); } + #[tokio::test(flavor = "multi_thread")] + async fn detects_nub_via_lockfile() { + let sandbox = create_empty_moon_sandbox(); + sandbox.create_file("nub.lock", ""); + + let plugin = sandbox.create_toolchain("javascript").await; + + let output = plugin + .initialize_toolchain(InitializeToolchainInput::default()) + .await; + + assert_eq!( + output.default_settings.get("packageManager").unwrap(), + &JsonValue::String("nub".into()) + ); + } + + #[tokio::test(flavor = "multi_thread")] + async fn detects_nub_via_package_json() { + let sandbox = create_empty_moon_sandbox(); + sandbox.create_file("package.json", r#"{ "packageManager": "nub@0.7.0" }"#); + + let plugin = sandbox.create_toolchain("javascript").await; + + let output = plugin + .initialize_toolchain(InitializeToolchainInput::default()) + .await; + + assert_eq!( + output.default_settings.get("packageManager").unwrap(), + &JsonValue::String("nub".into()) + ); + } + #[tokio::test(flavor = "multi_thread")] async fn detects_pnpm_via_lockfile() { let sandbox = create_empty_moon_sandbox(); diff --git a/toolchains/javascript/tests/tier2_env_test.rs b/toolchains/javascript/tests/tier2_env_test.rs index e654a360..4032a490 100644 --- a/toolchains/javascript/tests/tier2_env_test.rs +++ b/toolchains/javascript/tests/tier2_env_test.rs @@ -189,6 +189,38 @@ mod javascript_toolchain_tier2 { ); } + #[tokio::test(flavor = "multi_thread")] + async fn sets_field_for_nub() { + let mut sandbox = create_moon_sandbox("files"); + + sandbox + .host_funcs + .mock_load_toolchain_config(|_, _| json!({ "version": "1.2.3" })); + + let plugin = sandbox.create_toolchain("javascript").await; + + let output = plugin + .setup_environment(SetupEnvironmentInput { + root: VirtualPath::new(sandbox.path()), + toolchain_config: json!({ + "syncPackageManagerField": true, + "packageManager": "nub" + }), + ..Default::default() + }) + .await; + + assert_eq!( + output.changed_files, + [VirtualPath::new("/workspace/package.json")] + ); + assert!( + fs::read_to_string(sandbox.path().join("package.json")) + .unwrap() + .contains(r#""packageManager": "nub@1.2.3""#) + ); + } + #[tokio::test(flavor = "multi_thread")] async fn sets_field() { let mut sandbox = create_moon_sandbox("files"); diff --git a/toolchains/javascript/tests/tier2_test.rs b/toolchains/javascript/tests/tier2_test.rs index 5bcac4ef..31b45f31 100644 --- a/toolchains/javascript/tests/tier2_test.rs +++ b/toolchains/javascript/tests/tier2_test.rs @@ -596,6 +596,48 @@ mod javascript_toolchain_tier2 { assert_eq!(output.root.unwrap(), VirtualPath::new("/workspace/package")); } + #[tokio::test(flavor = "multi_thread")] + async fn finds_package_with_nub_lock() { + let sandbox = create_moon_sandbox("locate"); + sandbox.create_file("package/nub.lock", ""); + + let plugin = sandbox.create_toolchain("javascript").await; + + let output = plugin + .locate_dependencies_root(LocateDependenciesRootInput { + starting_dir: VirtualPath::new(sandbox.path().join("package/nested")), + toolchain_config: json!({ + "packageManager": "nub" + }), + ..Default::default() + }) + .await; + + assert!(output.members.is_none()); + assert_eq!(output.root.unwrap(), VirtualPath::new("/workspace/package")); + } + + #[tokio::test(flavor = "multi_thread")] + async fn finds_package_with_other_pm_lock_when_using_nub() { + let sandbox = create_moon_sandbox("locate"); + sandbox.create_file("package/pnpm-lock.yaml", ""); + + let plugin = sandbox.create_toolchain("javascript").await; + + let output = plugin + .locate_dependencies_root(LocateDependenciesRootInput { + starting_dir: VirtualPath::new(sandbox.path().join("package/nested")), + toolchain_config: json!({ + "packageManager": "nub" + }), + ..Default::default() + }) + .await; + + assert!(output.members.is_none()); + assert_eq!(output.root.unwrap(), VirtualPath::new("/workspace/package")); + } + #[tokio::test(flavor = "multi_thread")] async fn finds_package_with_pnpm_lock() { let sandbox = create_moon_sandbox("locate"); @@ -800,6 +842,58 @@ mod javascript_toolchain_tier2 { ); } + #[tokio::test(flavor = "multi_thread")] + async fn finds_workspace_with_nub_lock() { + let sandbox = create_moon_sandbox("locate"); + sandbox.create_file("workspace/nub.lock", ""); + + let plugin = sandbox.create_toolchain("javascript").await; + + let output = plugin + .locate_dependencies_root(LocateDependenciesRootInput { + starting_dir: VirtualPath::new( + sandbox.path().join("workspace/packages/a/nested"), + ), + toolchain_config: json!({ + "packageManager": "nub" + }), + ..Default::default() + }) + .await; + + assert_eq!(output.members.unwrap(), ["packages/*"]); + assert_eq!( + output.root.unwrap(), + VirtualPath::new("/workspace/workspace") + ); + } + + #[tokio::test(flavor = "multi_thread")] + async fn finds_workspace_with_pnpm_workspace_when_using_nub() { + let sandbox = create_moon_sandbox("locate"); + sandbox.create_file("workspace/pnpm-workspace.yaml", "packages: ['apps/*']"); + + let plugin = sandbox.create_toolchain("javascript").await; + + let output = plugin + .locate_dependencies_root(LocateDependenciesRootInput { + starting_dir: VirtualPath::new( + sandbox.path().join("workspace/packages/a/nested"), + ), + toolchain_config: json!({ + "packageManager": "nub" + }), + ..Default::default() + }) + .await; + + assert_eq!(output.members.unwrap(), ["apps/*"]); + assert_eq!( + output.root.unwrap(), + VirtualPath::new("/workspace/workspace") + ); + } + #[tokio::test(flavor = "multi_thread")] async fn finds_workspace_with_pnpm() { let sandbox = create_moon_sandbox("locate"); @@ -1390,6 +1484,216 @@ mod javascript_toolchain_tier2 { } } + mod nub { + use super::*; + + #[tokio::test(flavor = "multi_thread")] + async fn default_commands() { + let sandbox = create_empty_moon_sandbox(); + let plugin = sandbox.create_toolchain("javascript").await; + + let output = plugin + .install_dependencies(InstallDependenciesInput { + root: VirtualPath::new(sandbox.path()), + toolchain_config: json!({ + "packageManager": "nub", + "dedupeOnLockfileChange": true + }), + ..Default::default() + }) + .await; + + assert_eq!( + output.install_command.unwrap(), + ExecCommand::new( + ExecCommandInput::new("nub", ["install"]) + .cwd(plugin.plugin.to_virtual_path(sandbox.path())) + ) + ); + + assert_eq!( + output.dedupe_command.unwrap(), + ExecCommand::new( + ExecCommandInput::new("nub", ["dedupe"]) + .cwd(plugin.plugin.to_virtual_path(sandbox.path())) + ) + ); + } + + #[tokio::test(flavor = "multi_thread")] + async fn focused_commands() { + let sandbox = create_empty_moon_sandbox(); + let plugin = sandbox.create_toolchain("javascript").await; + + let output = plugin + .install_dependencies(InstallDependenciesInput { + root: VirtualPath::new(sandbox.path()), + toolchain_config: json!({ + "packageManager": "nub", + "dedupeOnLockfileChange": true + }), + packages: vec!["foo".into(), "@scope/bar".into()], + production: true, + ..Default::default() + }) + .await; + + assert_eq!( + output.install_command.unwrap(), + ExecCommand::new( + ExecCommandInput::new( + "nub", + [ + "install", + "--prod", + "--filter-prod", + "foo...", + "--filter-prod", + "@scope/bar..." + ] + ) + .cwd(plugin.plugin.to_virtual_path(sandbox.path())) + ) + ); + } + + #[tokio::test(flavor = "multi_thread")] + async fn inherits_args_from_nub_toolchain() { + let mut sandbox = create_empty_moon_sandbox(); + + sandbox + .host_funcs + .mock_load_toolchain_config(|_, _| json!({ "installArgs": ["-a", "b", "--c"]})); + + let plugin = sandbox.create_toolchain("javascript").await; + + let output = plugin + .install_dependencies(InstallDependenciesInput { + root: VirtualPath::new(sandbox.path()), + toolchain_config: json!({ + "packageManager": "nub", + "dedupeOnLockfileChange": true + }), + ..Default::default() + }) + .await; + + assert_eq!( + output.install_command.unwrap(), + ExecCommand::new( + ExecCommandInput::new("nub", ["install", "-a", "b", "--c"]) + .cwd(plugin.plugin.to_virtual_path(sandbox.path())) + ) + ); + } + + #[tokio::test(flavor = "multi_thread")] + async fn switches_to_ci_in_ci() { + let sandbox = create_empty_moon_sandbox(); + let plugin = sandbox + .create_toolchain_with_config("javascript", |cfg| { + cfg.host_environment(HostEnvironment { + ci: true, + ..Default::default() + }); + }) + .await; + + // Doesn't work without the lockfile + let output = plugin + .install_dependencies(InstallDependenciesInput { + root: VirtualPath::new(sandbox.path()), + toolchain_config: json!({ + "packageManager": "nub", + }), + ..Default::default() + }) + .await; + + assert_eq!( + output.install_command.unwrap(), + ExecCommand::new( + ExecCommandInput::new("nub", ["install"]) + .cwd(plugin.plugin.to_virtual_path(sandbox.path())) + ) + ); + + // Now works! + sandbox.create_file("nub.lock", ""); + + let output = plugin + .install_dependencies(InstallDependenciesInput { + root: VirtualPath::new(sandbox.path()), + toolchain_config: json!({ + "packageManager": "nub", + }), + ..Default::default() + }) + .await; + + assert_eq!( + output.install_command.unwrap(), + ExecCommand::new( + ExecCommandInput::new("nub", ["ci"]) + .cwd(plugin.plugin.to_virtual_path(sandbox.path())) + ) + ); + + // But not for production, since `nub ci` has no `--prod` + let output = plugin + .install_dependencies(InstallDependenciesInput { + root: VirtualPath::new(sandbox.path()), + toolchain_config: json!({ + "packageManager": "nub", + }), + production: true, + ..Default::default() + }) + .await; + + assert_eq!( + output.install_command.unwrap(), + ExecCommand::new( + ExecCommandInput::new("nub", ["install", "--prod"]) + .cwd(plugin.plugin.to_virtual_path(sandbox.path())) + ) + ); + } + + #[tokio::test(flavor = "multi_thread")] + async fn switches_to_ci_with_other_pm_lock() { + let sandbox = create_empty_moon_sandbox(); + sandbox.create_file("pnpm-lock.yaml", ""); + + let plugin = sandbox + .create_toolchain_with_config("javascript", |cfg| { + cfg.host_environment(HostEnvironment { + ci: true, + ..Default::default() + }); + }) + .await; + + let output = plugin + .install_dependencies(InstallDependenciesInput { + root: VirtualPath::new(sandbox.path()), + toolchain_config: json!({ + "packageManager": "nub", + }), + ..Default::default() + }) + .await; + + assert_eq!( + output.install_command.unwrap(), + ExecCommand::new( + ExecCommandInput::new("nub", ["ci"]) + .cwd(plugin.plugin.to_virtual_path(sandbox.path())) + ) + ); + } + } + mod pnpm { use super::*; @@ -2249,6 +2553,22 @@ mod javascript_toolchain_tier2 { assert_eq!(output.dependencies, expected_dependencies()); } + // `nub.lock` uses the pnpm lockfile format + #[tokio::test(flavor = "multi_thread")] + async fn parses_nub() { + let sandbox = create_lockfile_sandbox("nub"); + let plugin = sandbox.create_toolchain("javascript").await; + + let output = plugin + .parse_lock(ParseLockInput { + path: VirtualPath::new(sandbox.path().join("nub.lock")), + ..Default::default() + }) + .await; + + assert_eq!(output.dependencies, expected_base_dependencies()); + } + #[tokio::test(flavor = "multi_thread")] async fn parses_pnpm() { let sandbox = create_lockfile_sandbox("pnpm"); diff --git a/toolchains/node-depman/CHANGELOG.md b/toolchains/node-depman/CHANGELOG.md index d1b09134..0bef25a8 100644 --- a/toolchains/node-depman/CHANGELOG.md +++ b/toolchains/node-depman/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## Unreleased + +#### 🚀 Updates + +- Added unstable support for [Nub](https://nubjs.com/). + ## 1.0.4 #### 🚀 Updates diff --git a/toolchains/node-depman/Cargo.toml b/toolchains/node-depman/Cargo.toml index ca4cb6bc..ebb31858 100644 --- a/toolchains/node-depman/Cargo.toml +++ b/toolchains/node-depman/Cargo.toml @@ -2,7 +2,7 @@ name = "node_depman_toolchain" version = "1.0.4" edition = "2024" -description = "Node.js dependency managers (npm, pnpm, yarn) toolchain WASM plugin for moon." +description = "Node.js dependency managers (npm, pnpm, yarn, nub) toolchain WASM plugin for moon." authors = ["Miles Johnson"] license = "MIT" repository = "https://github.com/moonrepo/plugins" diff --git a/toolchains/node-depman/src/config.rs b/toolchains/node-depman/src/config.rs index a44e4459..f2d0d401 100644 --- a/toolchains/node-depman/src/config.rs +++ b/toolchains/node-depman/src/config.rs @@ -16,6 +16,20 @@ config_struct!( } ); +config_struct!( + /// Configures and enables the Nub toolchain. + /// Docs: https://moonrepo.dev/docs/config/toolchain#nub + #[derive(Config)] + pub struct NubToolchainConfig { + /// List of arguments to append to `nub install` commands. + /// These arguments are inherited by the JavaScript toolchain. + pub install_args: Vec, + + /// Configured version to download and install. + pub version: Option, + } +); + config_struct!( /// Configures and enables the pnpm toolchain. /// Docs: https://moonrepo.dev/docs/config/toolchain#pnpm diff --git a/toolchains/node-depman/src/tier1.rs b/toolchains/node-depman/src/tier1.rs index 3d091733..3ff2a6f0 100644 --- a/toolchains/node-depman/src/tier1.rs +++ b/toolchains/node-depman/src/tier1.rs @@ -21,7 +21,12 @@ pub fn register_toolchain( lock_file_names: vec!["package-lock.json".into(), "npm-shrinkwrap.json".into()], ..Default::default() }, - PackageManager::Nub => todo!(), + PackageManager::Nub => RegisterToolchainOutput { + config_file_globs: vec![".npmrc".into(), "nub.jsonc".into()], + exe_names: vec!["nub".into(), "nubx".into()], + lock_file_names: vec!["nub.lock".into()], + ..Default::default() + }, PackageManager::Pnpm | PackageManager::Pnpm11 | PackageManager::Pnpm12 => { RegisterToolchainOutput { config_file_globs: vec![ @@ -73,7 +78,7 @@ pub fn define_toolchain_config() -> FnResult> Ok(Json(DefineToolchainConfigOutput { schema: match manager { PackageManager::Npm => SchemaBuilder::build_root::(), - PackageManager::Nub => todo!(), + PackageManager::Nub => SchemaBuilder::build_root::(), PackageManager::Pnpm | PackageManager::Pnpm11 | PackageManager::Pnpm12 => { SchemaBuilder::build_root::() } diff --git a/toolchains/node-depman/src/tier2.rs b/toolchains/node-depman/src/tier2.rs index cd9babfc..a8857548 100644 --- a/toolchains/node-depman/src/tier2.rs +++ b/toolchains/node-depman/src/tier2.rs @@ -5,14 +5,19 @@ use extism_pdk::*; use moon_pdk::parse_toolchain_config_schema; use moon_pdk_api::*; use node_depman_tool::PackageManager; -use proto_pdk_api::{UnresolvedVersionSpec, VersionSpec}; #[plugin_fn] pub fn define_requirements( Json(_): Json, ) -> FnResult> { Ok(Json(DefineRequirementsOutput { - requires: vec!["node".into()], + // Nub is a standalone binary that can manage Node.js itself, + // while the other package managers are Node.js scripts + requires: if PackageManager::detect()?.is_nub() { + vec![] + } else { + vec!["node".into()] + }, })) } @@ -27,19 +32,18 @@ pub fn setup_environment( if manager.is_yarn() { let config = parse_toolchain_config_schema::(input.toolchain_config)?; - // TODO fix once moon is on proto 0.59 - if let Some(incompat_version) = &config.version { - let compat_version = UnresolvedVersionSpec::parse(incompat_version.to_string())?; - let compat_spec = match &compat_version { + if let Some(compat_version) = &config.version { + let compat_spec = match compat_version { + UnresolvedVersionSpec::Range(_) => None, UnresolvedVersionSpec::Requirement(req) => { - VersionSpec::parse(format!("{}.0.0", req.major.unwrap_or_default()))? + VersionSpec::parse(format!("{}.0.0", req.major.unwrap_or_default())).ok() } - _ => compat_version.to_resolved_spec(), + other => Some(other.to_resolved_spec()), }; - let new_manager = PackageManager::detect_from_version(&compat_spec)?; - - if new_manager == PackageManager::Yarn2to5 { + if let Some(compat_spec) = compat_spec + && PackageManager::detect_from_version(&compat_spec)? == PackageManager::Yarn2to5 + { for plugin in config.plugins { output.commands.push(ExecCommand::new( ExecCommandInput::new("yarn", ["plugin", "import", &plugin]) diff --git a/toolchains/node-depman/tests/tier1_test.rs b/toolchains/node-depman/tests/tier1_test.rs index adfa0d49..4aace750 100644 --- a/toolchains/node-depman/tests/tier1_test.rs +++ b/toolchains/node-depman/tests/tier1_test.rs @@ -27,6 +27,23 @@ mod node_depman_toolchain_tier1 { assert_eq!(output.vendor_dir_name.unwrap(), "node_modules"); } + #[tokio::test(flavor = "multi_thread")] + async fn handles_nub() { + let sandbox = create_empty_moon_sandbox(); + let plugin = sandbox.create_toolchain("nub").await; + + let output = plugin + .register_toolchain(RegisterToolchainInput { id: Id::raw("nub") }) + .await; + + assert_eq!(output.name, "nub"); + assert_eq!(output.config_file_globs, [".npmrc", "nub.jsonc"]); + assert_eq!(output.manifest_file_names, ["package.json"]); + assert_eq!(output.lock_file_names, ["nub.lock"]); + assert_eq!(output.exe_names, ["nub", "nubx"]); + assert_eq!(output.vendor_dir_name.unwrap(), "node_modules"); + } + #[tokio::test(flavor = "multi_thread")] async fn handles_pnpm() { let sandbox = create_empty_moon_sandbox(); diff --git a/toolchains/node-depman/tests/tier2_test.rs b/toolchains/node-depman/tests/tier2_test.rs index 9e2dd39a..622aa93a 100644 --- a/toolchains/node-depman/tests/tier2_test.rs +++ b/toolchains/node-depman/tests/tier2_test.rs @@ -24,6 +24,22 @@ mod node_depman_toolchain_tier2 { assert!(output.commands.is_empty()); } + #[tokio::test(flavor = "multi_thread")] + async fn does_nothing_for_nub() { + let sandbox = create_empty_moon_sandbox(); + let plugin = sandbox.create_toolchain("nub").await; + + let output = plugin + .setup_environment(SetupEnvironmentInput { + root: VirtualPath::new(sandbox.path()), + toolchain_config: json!({}), + ..Default::default() + }) + .await; + + assert!(output.commands.is_empty()); + } + #[tokio::test(flavor = "multi_thread")] async fn does_nothing_for_pnpm() { let sandbox = create_empty_moon_sandbox(); From 2c9965b1bb8054d34f06e4f40124e8fae6b00066 Mon Sep 17 00:00:00 2001 From: Miles Johnson Date: Wed, 12 Aug 2026 12:05:04 -0700 Subject: [PATCH 62/78] fix: Audit 08/11 (#181) --- Cargo.lock | 4 +- Cargo.toml | 2 +- toolchains/go/CHANGELOG.md | 12 ++ toolchains/go/src/tier2.rs | 63 +++++++- toolchains/go/tests/tier2_test.rs | 144 ++++++++++++++++++ toolchains/javascript/CHANGELOG.md | 5 + toolchains/javascript/src/lockfiles/bun.rs | 91 ++++++++++- toolchains/javascript/src/tier2.rs | 5 +- .../tests/__fixtures__/lockfiles/bun/bun.lock | 8 + toolchains/javascript/tests/tier2_test.rs | 23 ++- toolchains/node-depman/src/tier2.rs | 2 + toolchains/node/CHANGELOG.md | 6 + toolchains/node/src/config.rs | 1 + toolchains/node/src/tier2.rs | 30 ---- toolchains/node/tests/tier2_test.rs | 113 -------------- toolchains/python-pip/CHANGELOG.md | 6 + toolchains/python-pip/src/tier2.rs | 3 + toolchains/python-poetry/CHANGELOG.md | 6 + toolchains/python-poetry/src/tier2.rs | 3 + toolchains/python-uv/CHANGELOG.md | 6 + toolchains/python-uv/src/tier2.rs | 3 + toolchains/python/CHANGELOG.md | 7 + toolchains/python/src/tier1.rs | 24 ++- toolchains/python/src/tier2.rs | 21 +++ toolchains/python/tests/tier1_test.rs | 133 ++++++++++++++++ toolchains/rust/CHANGELOG.md | 8 + toolchains/rust/src/tier2_env.rs | 105 +++++++------ toolchains/rust/tests/tier2_env_test.rs | 141 ++++++++++++++++- 28 files changed, 773 insertions(+), 202 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 71c7ce1d..b6c60db1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3159,9 +3159,9 @@ dependencies = [ [[package]] name = "moon_pdk_api" -version = "2.1.0" +version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17e60b6592bdd68319ce9c0b5874e6cf67b7dd2cbdb4a72ca75de8232a2186b6" +checksum = "e85fa8551e29ac31db1c19d4914a101328a8c57cc4fa5d1db3ed402899bb97fc" dependencies = [ "derive_setters", "moon_common", diff --git a/Cargo.toml b/Cargo.toml index 8c32a678..7b415e82 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,7 +28,7 @@ toml = { version = "1.1.4", default-features = false, features = [ moon_common = { version = "2.0.8" } moon_config = { version = "2.1.1" } moon_pdk = { version = "2.1.0" } -moon_pdk_api = { version = "2.1.0" } +moon_pdk_api = { version = "2.1.2" } moon_pdk_test_utils = { version = "2.1.0" } moon_project = { version = "2.0.7" } moon_target = { version = "3.0.1" } diff --git a/toolchains/go/CHANGELOG.md b/toolchains/go/CHANGELOG.md index b3188a68..6c3431f1 100644 --- a/toolchains/go/CHANGELOG.md +++ b/toolchains/go/CHANGELOG.md @@ -1,5 +1,17 @@ # Changelog +## Unreleased + +#### 🐞 Fixes + +- Fixed configured `bins` not being reinstalled when their binaries were + uninstalled or deleted outside of moon. Missing binaries are now detected in + the globals directory, and only missing binaries are installed. Binaries + pinned to a version, branch, or commit are always installed, as their + installed version cannot be verified. +- The `force` option for `bins` entries is now respected, and will always + install the binary. + ## 1.4.5 #### 🚀 Updates diff --git a/toolchains/go/src/tier2.rs b/toolchains/go/src/tier2.rs index d6dcf02f..069a42de 100644 --- a/toolchains/go/src/tier2.rs +++ b/toolchains/go/src/tier2.rs @@ -359,6 +359,29 @@ pub fn parse_manifest( Ok(Json(output)) } +fn is_bin_installed( + env: &HostEnvironment, + globals_dir: Option<&VirtualPath>, + module: &str, + version: &str, +) -> bool { + // Without a registry to inspect (like Cargo's `.crates.toml`), we can't + // verify which version an installed binary is, so entries pinned to a + // version, branch, or commit are always included. Their command only + // executes when the environment fingerprint changes, like a version bump + if version != "latest" { + return false; + } + + let Some(globals_dir) = globals_dir else { + return false; + }; + + globals_dir + .join(env.os.get_exe_name(get_bin_name(module))) + .exists() +} + #[plugin_fn] pub fn setup_environment( Json(input): Json, @@ -374,18 +397,23 @@ pub fn setup_environment( let mut bins_by_version = BTreeMap::default(); for bin in &config.bins { - let name = match bin { - BinEntry::String(inner) => inner, + let (name, force) = match bin { + BinEntry::String(inner) => (inner.as_str(), false), BinEntry::Object(cfg) => { if cfg.local && env.ci { continue; - } else { - cfg.bin.as_str() } + + (cfg.bin.as_str(), cfg.force) } }; let (module, version) = name.split_once('@').unwrap_or((name, "latest")); + + if !force && is_bin_installed(env, input.globals_dir.as_ref(), module, version) { + continue; + } + let base_module = get_base_module(module); bins_by_version @@ -442,3 +470,30 @@ fn get_base_module(module: &str) -> String { base } + +// A major version suffix segment: v2, v3, etc, but never v0 or v1 +// https://go.dev/ref/mod#major-version-suffixes +fn is_version_segment(segment: &str) -> bool { + match segment.strip_prefix('v') { + Some(digits) => { + !digits.is_empty() + && !digits.starts_with('0') + && digits != "1" + && digits.chars().all(|c| c.is_ascii_digit()) + } + None => false, + } +} + +// The executable is named after the last segment of the module path, +// excluding a major version suffix: `github.com/foo/bar/v2` -> `bar` +fn get_bin_name(module: &str) -> &str { + let mut segments = module.rsplit('/'); + let last = segments.next().unwrap_or(module); + + if is_version_segment(last) { + return segments.next().unwrap_or(last); + } + + last +} diff --git a/toolchains/go/tests/tier2_test.rs b/toolchains/go/tests/tier2_test.rs index c7a4a588..104e5ba0 100644 --- a/toolchains/go/tests/tier2_test.rs +++ b/toolchains/go/tests/tier2_test.rs @@ -937,5 +937,149 @@ mod go_toolchain_tier2 { assert!(output.commands.is_empty()); } + + // https://github.com/moonrepo/plugins/issues/137 + + #[tokio::test(flavor = "multi_thread")] + async fn only_installs_missing_bins() { + let sandbox = create_empty_moon_sandbox(); + sandbox.create_file(".go/bin/gopls", ""); + sandbox.create_file(".go/bin/gopls.exe", ""); + + let plugin = sandbox.create_toolchain("go").await; + + let output = plugin + .setup_environment(SetupEnvironmentInput { + root: VirtualPath::new(sandbox.path()), + globals_dir: Some(VirtualPath::new(sandbox.path().join(".go/bin"))), + toolchain_config: json!({ + "bins": ["golang.org/x/tools/gopls", "github.com/revel/cmd/revel"] + }), + ..Default::default() + }) + .await; + + assert_eq!( + output.commands, + [ExecCommand::new( + ExecCommandInput::new("go", ["install", "-v", "github.com/revel/cmd/revel"],) + .cwd(plugin.plugin.to_virtual_path(sandbox.path())) + ) + .cache(CacheStrategy::Memory) + .label("go-bins-github.com/revel/cmd@latest")] + ); + } + + #[tokio::test(flavor = "multi_thread")] + async fn adds_no_commands_if_all_bins_installed() { + let sandbox = create_empty_moon_sandbox(); + sandbox.create_file(".go/bin/gopls", ""); + sandbox.create_file(".go/bin/gopls.exe", ""); + + let plugin = sandbox.create_toolchain("go").await; + + let output = plugin + .setup_environment(SetupEnvironmentInput { + root: VirtualPath::new(sandbox.path()), + globals_dir: Some(VirtualPath::new(sandbox.path().join(".go/bin"))), + toolchain_config: json!({ + "bins": ["golang.org/x/tools/gopls"] + }), + ..Default::default() + }) + .await; + + assert!(output.commands.is_empty()); + } + + #[tokio::test(flavor = "multi_thread")] + async fn checks_exe_name_excluding_major_version_suffix() { + let sandbox = create_empty_moon_sandbox(); + sandbox.create_file(".go/bin/revel", ""); + sandbox.create_file(".go/bin/revel.exe", ""); + + let plugin = sandbox.create_toolchain("go").await; + + let output = plugin + .setup_environment(SetupEnvironmentInput { + root: VirtualPath::new(sandbox.path()), + globals_dir: Some(VirtualPath::new(sandbox.path().join(".go/bin"))), + toolchain_config: json!({ + "bins": ["github.com/revel/cmd/revel/v2"] + }), + ..Default::default() + }) + .await; + + assert!(output.commands.is_empty()); + } + + #[tokio::test(flavor = "multi_thread")] + async fn always_installs_versioned_bins() { + let sandbox = create_empty_moon_sandbox(); + sandbox.create_file(".go/bin/gopls", ""); + sandbox.create_file(".go/bin/gopls.exe", ""); + + let plugin = sandbox.create_toolchain("go").await; + + let output = plugin + .setup_environment(SetupEnvironmentInput { + root: VirtualPath::new(sandbox.path()), + globals_dir: Some(VirtualPath::new(sandbox.path().join(".go/bin"))), + toolchain_config: json!({ + "bins": ["golang.org/x/tools/gopls@v0.16.0"] + }), + ..Default::default() + }) + .await; + + assert_eq!( + output.commands, + [ExecCommand::new( + ExecCommandInput::new( + "go", + ["install", "-v", "golang.org/x/tools/gopls@v0.16.0"], + ) + .cwd(plugin.plugin.to_virtual_path(sandbox.path())) + ) + .cache(CacheStrategy::Memory) + .label("go-bins-golang.org/x/tools@v0.16.0")] + ); + } + + #[tokio::test(flavor = "multi_thread")] + async fn always_installs_forced_bins() { + let sandbox = create_empty_moon_sandbox(); + sandbox.create_file(".go/bin/gopls", ""); + sandbox.create_file(".go/bin/gopls.exe", ""); + + let plugin = sandbox.create_toolchain("go").await; + + let output = plugin + .setup_environment(SetupEnvironmentInput { + root: VirtualPath::new(sandbox.path()), + globals_dir: Some(VirtualPath::new(sandbox.path().join(".go/bin"))), + toolchain_config: json!({ + "bins": [ + { + "bin": "golang.org/x/tools/gopls", + "force": true + } + ] + }), + ..Default::default() + }) + .await; + + assert_eq!( + output.commands, + [ExecCommand::new( + ExecCommandInput::new("go", ["install", "-v", "golang.org/x/tools/gopls"],) + .cwd(plugin.plugin.to_virtual_path(sandbox.path())) + ) + .cache(CacheStrategy::Memory) + .label("go-bins-golang.org/x/tools@latest")] + ); + } } } diff --git a/toolchains/javascript/CHANGELOG.md b/toolchains/javascript/CHANGELOG.md index 1782fa99..6ad0d356 100644 --- a/toolchains/javascript/CHANGELOG.md +++ b/toolchains/javascript/CHANGELOG.md @@ -11,6 +11,11 @@ present, otherwise from `package.json`. - Does not require the Node.js toolchain, as nub is a standalone binary. +#### 🐞 Fixes + +- Fixed `bun.lock` parsing failing on Git/GitHub dependencies that include both + package metadata (`dependencies`, `bin`, etc) and an integrity hash. + ## 1.2.2 #### 🚀 Updates diff --git a/toolchains/javascript/src/lockfiles/bun.rs b/toolchains/javascript/src/lockfiles/bun.rs index c8c7a37c..8e359c13 100644 --- a/toolchains/javascript/src/lockfiles/bun.rs +++ b/toolchains/javascript/src/lockfiles/bun.rs @@ -17,25 +17,46 @@ pub struct BunLockPackageJson { pub optional_dependencies: BTreeMap, } +// Entry shapes are defined by bun's lockfile serializer: +// https://github.com/oven-sh/bun/blob/main/src/install/lockfile/bun.lock.rs +// npm -> [ "name@version", registry, INFO, integrity ] +// git -> [ "name@git+repo", INFO, bun tag, integrity? ] +// github -> [ "name@github:user/repo", INFO, bun tag, integrity? ] +// tarball -> [ "name@url", INFO, integrity? ] +// symlink -> [ "name@link:path", INFO ] +// folder -> [ "name@file:path", INFO ] +// root -> [ "name@root:", INFO ] +// workspace -> [ "name@workspace:path" ] #[derive(Debug, Deserialize)] #[serde(untagged)] pub enum BunLockPackage { + // npm Dependency1( String, // identifier - String, // ??? + String, // registry JsonValue, // object String, // sha ), + // git/github with integrity Dependency2( String, // identifier JsonValue, // object + String, // bun tag String, // sha ), + // git/github without integrity, tarball Dependency3( String, // identifier JsonValue, // object + String, // bun tag or sha + ), + + // symlink, folder, root + Dependency4( + String, // identifier + JsonValue, // object ), // Must be last! @@ -79,21 +100,28 @@ pub fn parse_bun_lock(path: &VirtualPath, output: &mut ParseLockOutput) -> AnyRe continue; } - BunLockPackage::Dependency1(id, _unknown, _data, integrity) => { + BunLockPackage::Dependency1(id, _registry, _data, integrity) => { + let Some((name, version)) = parse_name_and_version(id, "") else { + continue; + }; + + (name, version, Some(integrity)) + } + BunLockPackage::Dependency2(id, _data, _tag, integrity) => { let Some((name, version)) = parse_name_and_version(id, "") else { continue; }; (name, version, Some(integrity)) } - BunLockPackage::Dependency2(id, _data, integrity) => { + BunLockPackage::Dependency3(id, _data, integrity) => { let Some((name, version)) = parse_name_and_version(id, "") else { continue; }; (name, version, Some(integrity)) } - BunLockPackage::Dependency3(id, _data) => { + BunLockPackage::Dependency4(id, _data) => { let Some((name, version)) = parse_name_and_version(id, "") else { continue; }; @@ -121,3 +149,58 @@ pub fn parse_bun_lockb(path: &VirtualPath, output: &mut ParseLockOutput) -> AnyR parse_yarn_lock_content(content.stdout.trim(), output) } + +#[cfg(test)] +mod tests { + use super::*; + + fn parse(content: &str) -> BunLockPackage { + json::parse(content).unwrap() + } + + #[test] + fn parses_npm_entry() { + assert!(matches!( + parse(r#"["csstype@3.1.3", "", {}, "sha512-M1uQ=="]"#), + BunLockPackage::Dependency1(..) + )); + } + + // https://github.com/moonrepo/plugins/issues/174 + #[test] + fn parses_github_entry_with_integrity() { + assert!(matches!( + parse( + r#"["@portkey-ai/gateway@github:Portkey-AI/gateway#ca77129", { "dependencies": { "async-retry": "^1.3.3" }, "bin": "build/start-server.js" }, "Portkey-AI-gateway-ca77129", "sha512-71lq=="]"# + ), + BunLockPackage::Dependency2(..) + )); + } + + // https://github.com/moonrepo/moon/issues/2049 + #[test] + fn parses_github_entry_without_integrity() { + assert!(matches!( + parse( + r#"["uWebSockets.js@github:uNetworking/uWebSockets.js#6609a88", {}, "uNetworking-uWebSockets.js-6609a88"]"# + ), + BunLockPackage::Dependency3(..) + )); + } + + #[test] + fn parses_symlink_entry() { + assert!(matches!( + parse(r#"["local-lib@link:packages/local-lib", {}]"#), + BunLockPackage::Dependency4(..) + )); + } + + #[test] + fn parses_workspace_entry() { + assert!(matches!( + parse(r#"["a@workspace:packages/a"]"#), + BunLockPackage::Workspace(..) + )); + } +} diff --git a/toolchains/javascript/src/tier2.rs b/toolchains/javascript/src/tier2.rs index 082ecc3c..8be5aaf4 100644 --- a/toolchains/javascript/src/tier2.rs +++ b/toolchains/javascript/src/tier2.rs @@ -203,7 +203,10 @@ pub fn define_requirements( ) -> FnResult> { let config = parse_toolchain_config_schema::(input.toolchain_config)?; - let mut output = DefineRequirementsOutput::default(); + let mut output = DefineRequirementsOutput { + for_setup_toolchain: true, + ..Default::default() + }; if let Some(package_manager) = config.package_manager { if !package_manager.is_standalone() { diff --git a/toolchains/javascript/tests/__fixtures__/lockfiles/bun/bun.lock b/toolchains/javascript/tests/__fixtures__/lockfiles/bun/bun.lock index f77a9680..2eecfb41 100644 --- a/toolchains/javascript/tests/__fixtures__/lockfiles/bun/bun.lock +++ b/toolchains/javascript/tests/__fixtures__/lockfiles/bun/bun.lock @@ -2,6 +2,10 @@ "lockfileVersion": 1, "workspaces": { "": { + "dependencies": { + "@portkey-ai/gateway": "git+https://github.com/Portkey-AI/gateway.git#v1.15.2", + "uWebSockets.js": "github:uNetworking/uWebSockets.js#6609a88", + }, "devDependencies": { "typescript": "^5.9.0", }, @@ -31,6 +35,8 @@ }, }, "packages": { + "@portkey-ai/gateway": ["@portkey-ai/gateway@github:Portkey-AI/gateway#ca77129", { "dependencies": { "react": "^19.1.0" }, "bin": "build/start-server.js" }, "Portkey-AI-gateway-ca77129", "sha512-71lqRjKGGxMSs4l6DdrzXHkGvitFdCsWxLbxYVfbYDTfhqOL0dJbmqjyrKObjSTbQqCXTh2XZQeI2Un0FN7pig=="], + "a": ["a@workspace:a"], "b": ["b@workspace:b"], @@ -48,5 +54,7 @@ "solid-js": ["solid-js@1.9.9", "", { "dependencies": { "csstype": "^3.1.0", "seroval": "~1.3.0", "seroval-plugins": "~1.3.0" } }, "sha512-A0ZBPJQldAeGCTW0YRYJmt7RCeh5rbFfPZ2aOttgYnctHE7HgKeHCBB/PVc2P7eOfmNXqMFFFoYYdm3S4dcbkA=="], "typescript": ["typescript@5.9.2", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A=="], + + "uWebSockets.js": ["uWebSockets.js@github:uNetworking/uWebSockets.js#6609a88", {}, "uNetworking-uWebSockets.js-6609a88"], } } diff --git a/toolchains/javascript/tests/tier2_test.rs b/toolchains/javascript/tests/tier2_test.rs index 31b45f31..d857b7b0 100644 --- a/toolchains/javascript/tests/tier2_test.rs +++ b/toolchains/javascript/tests/tier2_test.rs @@ -2443,7 +2443,28 @@ mod javascript_toolchain_tier2 { }) .await; - assert_eq!(output.dependencies, expected_dependencies()); + // GitHub dependencies only exist in the bun lockfile fixture: + // with package metadata + integrity, and without either + let mut expected = expected_dependencies(); + expected.insert( + "@portkey-ai/gateway".into(), + vec![LockDependency { + hash: Some( + "sha512-71lqRjKGGxMSs4l6DdrzXHkGvitFdCsWxLbxYVfbYDTfhqOL0dJbmqjyrKObjSTbQqCXTh2XZQeI2Un0FN7pig==" + .into() + ), + ..Default::default() + }], + ); + expected.insert( + "uWebSockets.js".into(), + vec![LockDependency { + hash: Some("uNetworking-uWebSockets.js-6609a88".into()), + ..Default::default() + }], + ); + + assert_eq!(output.dependencies, expected); } // #[tokio::test(flavor = "multi_thread")] diff --git a/toolchains/node-depman/src/tier2.rs b/toolchains/node-depman/src/tier2.rs index a8857548..0bad4297 100644 --- a/toolchains/node-depman/src/tier2.rs +++ b/toolchains/node-depman/src/tier2.rs @@ -18,6 +18,8 @@ pub fn define_requirements( } else { vec!["node".into()] }, + for_setup_environment: false, + for_setup_toolchain: true, })) } diff --git a/toolchains/node/CHANGELOG.md b/toolchains/node/CHANGELOG.md index 605c3be2..2047e588 100644 --- a/toolchains/node/CHANGELOG.md +++ b/toolchains/node/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## Unreleased + +#### 🚀 Updates + +- Deprecated the `syncVersionManagerConfig` setting (it never worked correctly). + ## 1.0.3 #### 🚀 Updates diff --git a/toolchains/node/src/config.rs b/toolchains/node/src/config.rs index cdd0689d..44b7b116 100644 --- a/toolchains/node/src/config.rs +++ b/toolchains/node/src/config.rs @@ -36,6 +36,7 @@ config_struct!( pub profile_execution: Option, /// When `version` is defined, syncs the version to the chosen config. + #[deprecated] pub sync_version_manager_config: Option, /// Configured version to download and install. diff --git a/toolchains/node/src/tier2.rs b/toolchains/node/src/tier2.rs index 19366bff..e30f5b64 100644 --- a/toolchains/node/src/tier2.rs +++ b/toolchains/node/src/tier2.rs @@ -4,36 +4,6 @@ use crate::config::*; use extism_pdk::*; use moon_pdk::{VirtualPathExt, parse_toolchain_config}; use moon_pdk_api::*; -use starbase_utils::fs; - -#[plugin_fn] -pub fn setup_environment( - Json(input): Json, -) -> FnResult> { - let config = parse_toolchain_config::(input.toolchain_config)?; - let mut output = SetupEnvironmentOutput::default(); - - // Sync version manager - if let Some(version_manager) = config.sync_version_manager_config - && let Some(version) = config.version - { - let (op, file) = Operation::track("sync-version-manager", || { - let rc_path = input.root.join(match version_manager { - NodeVersionManager::Nodenv => ".node-version", - NodeVersionManager::Nvm => ".nvmrc", - }); - - fs::write_file(&rc_path, version.to_partial_string())?; - - Ok(rc_path) - })?; - - output.operations.push(op); - output.changed_files.push(file); - } - - Ok(Json(output)) -} #[plugin_fn] pub fn extend_task_command( diff --git a/toolchains/node/tests/tier2_test.rs b/toolchains/node/tests/tier2_test.rs index 1ca92d27..63d7bfa7 100644 --- a/toolchains/node/tests/tier2_test.rs +++ b/toolchains/node/tests/tier2_test.rs @@ -1,123 +1,10 @@ use moon_pdk_api::*; use moon_pdk_test_utils::create_empty_moon_sandbox; use serde_json::json; -use std::fs; mod node_toolchain_tier2 { use super::*; - mod setup_environment { - use super::*; - - mod sync_toolchain_config { - use super::*; - - #[tokio::test(flavor = "multi_thread")] - async fn does_nothing_if_no_version() { - let sandbox = create_empty_moon_sandbox(); - let plugin = sandbox.create_toolchain("node").await; - - let output = plugin - .setup_environment(SetupEnvironmentInput { - root: VirtualPath::new(sandbox.path()), - toolchain_config: json!({ - "syncVersionManagerConfig": "nvm", - "version": null - }), - ..Default::default() - }) - .await; - - assert!(output.operations.is_empty()); - assert!(output.changed_files.is_empty()); - } - - #[tokio::test(flavor = "multi_thread")] - async fn does_nothing_if_disabled() { - let sandbox = create_empty_moon_sandbox(); - let plugin = sandbox.create_toolchain("node").await; - - let output = plugin - .setup_environment(SetupEnvironmentInput { - root: VirtualPath::new(sandbox.path()), - toolchain_config: json!({ - "syncVersionManagerConfig": null, - "version": "20.1" - }), - ..Default::default() - }) - .await; - - assert!(output.operations.is_empty()); - assert!(output.changed_files.is_empty()); - } - - #[tokio::test(flavor = "multi_thread")] - async fn writes_nvm_version() { - let sandbox = create_empty_moon_sandbox(); - let plugin = sandbox.create_toolchain("node").await; - - let output = plugin - .setup_environment(SetupEnvironmentInput { - root: VirtualPath::new(sandbox.path()), - toolchain_config: json!({ - "syncVersionManagerConfig": "nvm", - "version": "20.1" - }), - ..Default::default() - }) - .await; - - assert!( - output - .operations - .iter() - .any(|op| op.id == "sync-version-manager") - ); - assert_eq!( - output.changed_files, - [VirtualPath::new("/workspace/.nvmrc")] - ); - assert_eq!( - fs::read_to_string(sandbox.path().join(".nvmrc")).unwrap(), - "20.1" - ); - } - - #[tokio::test(flavor = "multi_thread")] - async fn writes_nodenv_version() { - let sandbox = create_empty_moon_sandbox(); - let plugin = sandbox.create_toolchain("node").await; - - let output = plugin - .setup_environment(SetupEnvironmentInput { - root: VirtualPath::new(sandbox.path()), - toolchain_config: json!({ - "syncVersionManagerConfig": "nodenv", - "version": "20.1" - }), - ..Default::default() - }) - .await; - - assert!( - output - .operations - .iter() - .any(|op| op.id == "sync-version-manager") - ); - assert_eq!( - output.changed_files, - [VirtualPath::new("/workspace/.node-version")] - ); - assert_eq!( - fs::read_to_string(sandbox.path().join(".node-version")).unwrap(), - "20.1" - ); - } - } - } - mod extend_task_command { use super::*; diff --git a/toolchains/python-pip/CHANGELOG.md b/toolchains/python-pip/CHANGELOG.md index 8407898b..92526612 100644 --- a/toolchains/python-pip/CHANGELOG.md +++ b/toolchains/python-pip/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## Unreleased + +#### 🚀 Updates + +- Ensures that Python is installed before setting up this toolchain. + ## 0.1.3 #### 🚀 Updates diff --git a/toolchains/python-pip/src/tier2.rs b/toolchains/python-pip/src/tier2.rs index 283eb09c..326ccb64 100644 --- a/toolchains/python-pip/src/tier2.rs +++ b/toolchains/python-pip/src/tier2.rs @@ -9,5 +9,8 @@ pub fn define_requirements( ) -> FnResult> { Ok(Json(DefineRequirementsOutput { requires: vec!["unstable_python".into()], + for_setup_environment: false, + // Requires python to run pip + for_setup_toolchain: true, })) } diff --git a/toolchains/python-poetry/CHANGELOG.md b/toolchains/python-poetry/CHANGELOG.md index 83e9ba1b..6a155f8e 100644 --- a/toolchains/python-poetry/CHANGELOG.md +++ b/toolchains/python-poetry/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## Unreleased + +#### 🚀 Updates + +- Ensures that Python is installed before setting up this toolchain. + ## 0.1.1 #### 🚀 Updates diff --git a/toolchains/python-poetry/src/tier2.rs b/toolchains/python-poetry/src/tier2.rs index 283eb09c..196b3690 100644 --- a/toolchains/python-poetry/src/tier2.rs +++ b/toolchains/python-poetry/src/tier2.rs @@ -9,5 +9,8 @@ pub fn define_requirements( ) -> FnResult> { Ok(Json(DefineRequirementsOutput { requires: vec!["unstable_python".into()], + for_setup_environment: false, + // Requires python to run poetry + for_setup_toolchain: true, })) } diff --git a/toolchains/python-uv/CHANGELOG.md b/toolchains/python-uv/CHANGELOG.md index 44374b21..a3d027f9 100644 --- a/toolchains/python-uv/CHANGELOG.md +++ b/toolchains/python-uv/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## Unreleased + +#### 🚀 Updates + +- Ensures that Python is installed before setting up this toolchain. + ## 0.1.4 #### 🚀 Updates diff --git a/toolchains/python-uv/src/tier2.rs b/toolchains/python-uv/src/tier2.rs index 283eb09c..aece7c9b 100644 --- a/toolchains/python-uv/src/tier2.rs +++ b/toolchains/python-uv/src/tier2.rs @@ -9,5 +9,8 @@ pub fn define_requirements( ) -> FnResult> { Ok(Json(DefineRequirementsOutput { requires: vec!["unstable_python".into()], + for_setup_environment: false, + // Aligns with other package managers + for_setup_toolchain: true, })) } diff --git a/toolchains/python/CHANGELOG.md b/toolchains/python/CHANGELOG.md index 8354a977..3d4d14d5 100644 --- a/toolchains/python/CHANGELOG.md +++ b/toolchains/python/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## Unreleased + +#### 🚀 Updates + +- Ensures that package manager toolchains are installed before setting up the environment. +- Added support to the Docker pruning workflow where we remove `.venv` directories for non-focused projects (those that were explicitly scaffolded). + ## 0.2.1 #### 🚀 Updates diff --git a/toolchains/python/src/tier1.rs b/toolchains/python/src/tier1.rs index d268336a..69b55596 100644 --- a/toolchains/python/src/tier1.rs +++ b/toolchains/python/src/tier1.rs @@ -1,9 +1,10 @@ use crate::config::{PythonPackageManager, PythonToolchainConfig}; use extism_pdk::*; use moon_config::LanguageType; -use moon_pdk::parse_toolchain_config; +use moon_pdk::{parse_toolchain_config, parse_toolchain_config_schema}; use moon_pdk_api::*; use schematic::SchemaBuilder; +use starbase_utils::fs; use starbase_utils::json::JsonValue; use toolchain_common::enable_tracing; @@ -141,3 +142,24 @@ pub fn define_docker_metadata( scaffold_globs: vec![], })) } + +#[plugin_fn] +pub fn prune_docker(Json(input): Json) -> FnResult> { + let config = parse_toolchain_config_schema::(input.toolchain_config)?; + let mut output = PruneDockerOutput::default(); + + if input.docker_config.delete_vendor_directories { + for dep in input.project_dependencies { + let dep_root = input.context.get_project_root(&dep); + let venv_dir = dep_root.join(&config.venv_name); + + if venv_dir.exists() { + fs::remove_dir_all(&venv_dir)?; + + output.changed_files.push(venv_dir); + } + } + } + + Ok(Json(output)) +} diff --git a/toolchains/python/src/tier2.rs b/toolchains/python/src/tier2.rs index 841364fd..bafffb5a 100644 --- a/toolchains/python/src/tier2.rs +++ b/toolchains/python/src/tier2.rs @@ -13,6 +13,27 @@ use pep508_rs::Requirement; use std::collections::BTreeMap; use std::path::PathBuf; +#[plugin_fn] +pub fn define_requirements( + Json(input): Json, +) -> FnResult> { + let config = parse_toolchain_config_schema::(input.toolchain_config)?; + + Ok(Json(DefineRequirementsOutput { + requires: match config.package_manager { + Some(package_manager) => vec![format!("unstable_{package_manager}")], + None => vec![], + }, + // We must ensure package managers are fully installed + // before we setup the environment, otherwise commands fail + for_setup_environment: true, + // However, we don't want this when we setup this toolchain, + // because the package managers depend on us, and it would + // introduce a cycle + for_setup_toolchain: false, + })) +} + #[plugin_fn] pub fn extend_project_graph( Json(input): Json, diff --git a/toolchains/python/tests/tier1_test.rs b/toolchains/python/tests/tier1_test.rs index a89d8588..a2d2b82a 100644 --- a/toolchains/python/tests/tier1_test.rs +++ b/toolchains/python/tests/tier1_test.rs @@ -1,3 +1,4 @@ +use moon_config::DockerPruneConfig; use moon_pdk_api::*; use moon_pdk_test_utils::create_empty_moon_sandbox; use serde_json::json; @@ -87,4 +88,136 @@ mod python_toolchain_tier1 { assert_eq!(output.default_image.unwrap(), "python:3.10"); } } + + mod prune_docker { + use super::*; + + fn create_project_fragment(id: &str) -> ProjectFragment { + ProjectFragment { + id: Id::raw(id), + source: id.into(), + ..Default::default() + } + } + + #[tokio::test(flavor = "multi_thread")] + async fn does_nothing_if_disabled() { + let sandbox = create_empty_moon_sandbox(); + sandbox.create_file("a/.venv/pyvenv.cfg", ""); + + let plugin = sandbox.create_toolchain("python").await; + + let output = plugin + .prune_docker(PruneDockerInput { + docker_config: DockerPruneConfig { + delete_vendor_directories: false, + ..Default::default() + }, + project_dependencies: vec![create_project_fragment("a")], + root: VirtualPath::new(sandbox.path()), + toolchain_config: json!({}), + ..Default::default() + }) + .await; + + assert!(sandbox.path().join("a/.venv").exists()); + + assert!(output.changed_files.is_empty()); + } + + #[tokio::test(flavor = "multi_thread")] + async fn does_nothing_if_no_venv_dirs() { + let sandbox = create_empty_moon_sandbox(); + let plugin = sandbox.create_toolchain("python").await; + + let output = plugin + .prune_docker(PruneDockerInput { + docker_config: DockerPruneConfig { + delete_vendor_directories: true, + ..Default::default() + }, + project_dependencies: vec![create_project_fragment("a")], + root: VirtualPath::new(sandbox.path()), + toolchain_config: json!({}), + ..Default::default() + }) + .await; + + assert!(output.changed_files.is_empty()); + } + + #[tokio::test(flavor = "multi_thread")] + async fn removes_venv_dirs_from_dependency_projects() { + let sandbox = create_empty_moon_sandbox(); + sandbox.create_file("a/.venv/pyvenv.cfg", ""); + sandbox.create_file("b/.venv/pyvenv.cfg", ""); + sandbox.create_file("c/.venv/pyvenv.cfg", ""); + + let plugin = sandbox.create_toolchain("python").await; + + let output = plugin + .prune_docker(PruneDockerInput { + docker_config: DockerPruneConfig { + delete_vendor_directories: true, + ..Default::default() + }, + project_dependencies: vec![ + create_project_fragment("a"), + create_project_fragment("b"), + ], + root: VirtualPath::new(sandbox.path()), + toolchain_config: json!({}), + ..Default::default() + }) + .await; + + assert!(!sandbox.path().join("a/.venv").exists()); + assert!(!sandbox.path().join("b/.venv").exists()); + + // Not a dependency, so remains + assert!(sandbox.path().join("c/.venv").exists()); + + assert_eq!( + output.changed_files, + [ + VirtualPath::new("/workspace/a/.venv"), + VirtualPath::new("/workspace/b/.venv") + ] + ); + } + + #[tokio::test(flavor = "multi_thread")] + async fn removes_custom_named_venv_dirs() { + let sandbox = create_empty_moon_sandbox(); + sandbox.create_file("a/venv/pyvenv.cfg", ""); + sandbox.create_file("a/.venv/pyvenv.cfg", ""); + + let plugin = sandbox.create_toolchain("python").await; + + let output = plugin + .prune_docker(PruneDockerInput { + docker_config: DockerPruneConfig { + delete_vendor_directories: true, + ..Default::default() + }, + project_dependencies: vec![create_project_fragment("a")], + root: VirtualPath::new(sandbox.path()), + toolchain_config: json!({ + "venvName": "venv" + }), + ..Default::default() + }) + .await; + + assert!(!sandbox.path().join("a/venv").exists()); + + // Not the configured name, so remains + assert!(sandbox.path().join("a/.venv").exists()); + + assert_eq!( + output.changed_files, + [VirtualPath::new("/workspace/a/venv")] + ); + } + } } diff --git a/toolchains/rust/CHANGELOG.md b/toolchains/rust/CHANGELOG.md index 84ca2f04..168ff78f 100644 --- a/toolchains/rust/CHANGELOG.md +++ b/toolchains/rust/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog +## Unreleased + +#### 🐞 Fixes + +- Fixed configured `bins` not being reinstalled when their binaries were + uninstalled or deleted outside of moon. Only missing binaries are now + installed. + ## 1.0.8 #### 🚀 Updates diff --git a/toolchains/rust/src/tier2_env.rs b/toolchains/rust/src/tier2_env.rs index 4f8260bc..47523b18 100644 --- a/toolchains/rust/src/tier2_env.rs +++ b/toolchains/rust/src/tier2_env.rs @@ -11,6 +11,20 @@ fn create_command(bin: &str, args: Vec<&str>, cwd: &VirtualPath) -> ExecCommand ExecCommand::new(ExecCommandInput::new(bin, args).cwd(cwd.to_owned())) } +fn is_bin_installed(env: &HostEnvironment, globals_dir: Option<&VirtualPath>, spec: &str) -> bool { + let Some(globals_dir) = globals_dir else { + return false; + }; + + // Entries may be suffixed with a version: `cargo-nextest@0.9.52` + let name = match spec.split_once('@') { + Some((prefix, _)) => prefix, + None => spec, + }; + + globals_dir.join(env.os.get_exe_name(name)).exists() +} + #[plugin_fn] pub fn setup_environment( Json(input): Json, @@ -66,68 +80,73 @@ pub fn setup_environment( if !config.bins.is_empty() { let env = get_host_environment()?; - // Only install if we can't find the binary - if input - .globals_dir - .is_none_or(|dir| !dir.join(env.os.get_exe_name("cargo-binstall")).exists()) - { - let binstall_package = if let Some(version) = &config.binstall_version { - format!("cargo-binstall@{version}") - } else { - "cargo-binstall".into() - }; - - output.commands.push( - create_command( - "cargo", - vec!["install", &binstall_package, "--force", "--locked"], - &input.root, - ) - .cache(CacheStrategy::Memory) - .label("cargo-binstall"), - ); - } - let mut force_bins = vec![]; let mut non_force_bins = vec![]; for bin in &config.bins { match bin { BinEntry::String(inner) => { - non_force_bins.push(inner.as_str()); + if !is_bin_installed(env, input.globals_dir.as_ref(), inner) { + non_force_bins.push(inner.as_str()); + } } BinEntry::Object(cfg) => { if cfg.local && env.ci { continue; } else if cfg.force { force_bins.push(cfg.bin.as_str()); - } else { + } else if !is_bin_installed(env, input.globals_dir.as_ref(), &cfg.bin) { non_force_bins.push(cfg.bin.as_str()); } } }; } - if !force_bins.is_empty() { - let mut args = vec!["binstall", "--no-confirm", "--log-level", "info", "--force"]; - args.extend(force_bins); - - output.commands.push( - create_command("cargo", args, &input.root) - .cache(CacheStrategy::Memory) - .label("cargo-bins-forced"), - ); - } - - if !non_force_bins.is_empty() { - let mut args = vec!["binstall", "--no-confirm", "--log-level", "info"]; - args.extend(non_force_bins); - - output.commands.push( - create_command("cargo", args, &input.root) + if !force_bins.is_empty() || !non_force_bins.is_empty() { + // Only install if we can't find the binary + if input + .globals_dir + .as_ref() + .is_none_or(|dir| !dir.join(env.os.get_exe_name("cargo-binstall")).exists()) + { + let binstall_package = if let Some(version) = &config.binstall_version { + format!("cargo-binstall@{version}") + } else { + "cargo-binstall".into() + }; + + output.commands.push( + create_command( + "cargo", + vec!["install", &binstall_package, "--force", "--locked"], + &input.root, + ) .cache(CacheStrategy::Memory) - .label("cargo-bins"), - ); + .label("cargo-binstall"), + ); + } + + if !force_bins.is_empty() { + let mut args = vec!["binstall", "--no-confirm", "--log-level", "info", "--force"]; + args.extend(force_bins); + + output.commands.push( + create_command("cargo", args, &input.root) + .cache(CacheStrategy::Memory) + .label("cargo-bins-forced"), + ); + } + + if !non_force_bins.is_empty() { + let mut args = vec!["binstall", "--no-confirm", "--log-level", "info"]; + args.extend(non_force_bins); + + output.commands.push( + create_command("cargo", args, &input.root) + .cache(CacheStrategy::Memory) + .label("cargo-bins"), + ); + } } } diff --git a/toolchains/rust/tests/tier2_env_test.rs b/toolchains/rust/tests/tier2_env_test.rs index 877150d2..d683eb98 100644 --- a/toolchains/rust/tests/tier2_env_test.rs +++ b/toolchains/rust/tests/tier2_env_test.rs @@ -510,8 +510,8 @@ mod rust_toolchain_tier2 { }) .await; - // binstall command - assert_eq!(output.commands.len(), 1); + // no binstall command either, since there's nothing to install + assert!(output.commands.is_empty()); } #[tokio::test(flavor = "multi_thread")] @@ -599,5 +599,142 @@ mod rust_toolchain_tier2 { .label("cargo-bins")] ); } + + // https://github.com/moonrepo/plugins/issues/137 + + #[tokio::test(flavor = "multi_thread")] + async fn only_installs_missing_bins() { + let sandbox = create_empty_moon_sandbox(); + sandbox.create_file(".cargo/bin/cargo-nextest", ""); + sandbox.create_file(".cargo/bin/cargo-nextest.exe", ""); + + let plugin = sandbox.create_toolchain("rust").await; + + let output = plugin + .setup_environment(SetupEnvironmentInput { + root: VirtualPath::new(sandbox.path()), + globals_dir: Some(VirtualPath::new(sandbox.path().join(".cargo/bin"))), + toolchain_config: json!({ + "bins": ["cargo-nextest", "just"] + }), + ..Default::default() + }) + .await; + + assert_eq!( + output.commands, + [ + ExecCommand::new( + ExecCommandInput::new( + "cargo", + ["install", "cargo-binstall", "--force", "--locked"], + ) + .cwd(plugin.plugin.to_virtual_path(sandbox.path())) + ) + .cache(CacheStrategy::Memory) + .label("cargo-binstall"), + ExecCommand::new( + ExecCommandInput::new( + "cargo", + ["binstall", "--no-confirm", "--log-level", "info", "just"], + ) + .cwd(plugin.plugin.to_virtual_path(sandbox.path())) + ) + .cache(CacheStrategy::Memory) + .label("cargo-bins") + ] + ); + } + + #[tokio::test(flavor = "multi_thread")] + async fn adds_no_commands_if_all_bins_installed() { + let sandbox = create_empty_moon_sandbox(); + sandbox.create_file(".cargo/bin/cargo-nextest", ""); + sandbox.create_file(".cargo/bin/cargo-nextest.exe", ""); + + let plugin = sandbox.create_toolchain("rust").await; + + let output = plugin + .setup_environment(SetupEnvironmentInput { + root: VirtualPath::new(sandbox.path()), + globals_dir: Some(VirtualPath::new(sandbox.path().join(".cargo/bin"))), + toolchain_config: json!({ + "bins": ["cargo-nextest"] + }), + ..Default::default() + }) + .await; + + assert!(output.commands.is_empty()); + } + + #[tokio::test(flavor = "multi_thread")] + async fn skips_installed_versioned_bins() { + let sandbox = create_empty_moon_sandbox(); + sandbox.create_file(".cargo/bin/cargo-nextest", ""); + sandbox.create_file(".cargo/bin/cargo-nextest.exe", ""); + + let plugin = sandbox.create_toolchain("rust").await; + + let output = plugin + .setup_environment(SetupEnvironmentInput { + root: VirtualPath::new(sandbox.path()), + globals_dir: Some(VirtualPath::new(sandbox.path().join(".cargo/bin"))), + toolchain_config: json!({ + "bins": ["cargo-nextest@0.9.52"] + }), + ..Default::default() + }) + .await; + + assert!(output.commands.is_empty()); + } + + #[tokio::test(flavor = "multi_thread")] + async fn always_installs_forced_bins() { + let sandbox = create_empty_moon_sandbox(); + sandbox.create_file(".cargo/bin/cargo-binstall", ""); + sandbox.create_file(".cargo/bin/cargo-binstall.exe", ""); + sandbox.create_file(".cargo/bin/cargo-nextest", ""); + sandbox.create_file(".cargo/bin/cargo-nextest.exe", ""); + + let plugin = sandbox.create_toolchain("rust").await; + + let output = plugin + .setup_environment(SetupEnvironmentInput { + root: VirtualPath::new(sandbox.path()), + globals_dir: Some(VirtualPath::new(sandbox.path().join(".cargo/bin"))), + toolchain_config: json!({ + "bins": [ + { + "bin": "cargo-nextest", + "force": true + } + ] + }), + ..Default::default() + }) + .await; + + assert_eq!( + output.commands, + [ExecCommand::new( + ExecCommandInput::new( + "cargo", + [ + "binstall", + "--no-confirm", + "--log-level", + "info", + "--force", + "cargo-nextest", + ], + ) + .cwd(plugin.plugin.to_virtual_path(sandbox.path())) + ) + .cache(CacheStrategy::Memory) + .label("cargo-bins-forced")] + ); + } } } From 75a170fda23af75c7453998ce3a03d1cc3cc67c7 Mon Sep 17 00:00:00 2001 From: Miles Johnson Date: Wed, 12 Aug 2026 12:07:07 -0700 Subject: [PATCH 63/78] chore: Release --- Cargo.lock | 2 +- toolchains/go/CHANGELOG.md | 2 +- toolchains/go/Cargo.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b6c60db1..9646160f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1984,7 +1984,7 @@ dependencies = [ [[package]] name = "go_toolchain" -version = "1.4.5" +version = "1.4.6" dependencies = [ "extism-pdk", "go_tool", diff --git a/toolchains/go/CHANGELOG.md b/toolchains/go/CHANGELOG.md index 6c3431f1..08aad279 100644 --- a/toolchains/go/CHANGELOG.md +++ b/toolchains/go/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## Unreleased +## 1.4.6 #### 🐞 Fixes diff --git a/toolchains/go/Cargo.toml b/toolchains/go/Cargo.toml index 3be17187..6e5bf2cc 100644 --- a/toolchains/go/Cargo.toml +++ b/toolchains/go/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "go_toolchain" -version = "1.4.5" +version = "1.4.6" edition = "2024" description = "Go toolchain WASM plugin for moon." authors = ["Miles Johnson"] From e4d06783a5113e0a1580f93922484d6b1c509154 Mon Sep 17 00:00:00 2001 From: Miles Johnson Date: Wed, 12 Aug 2026 12:07:20 -0700 Subject: [PATCH 64/78] chore: Release --- Cargo.lock | 2 +- toolchains/node/CHANGELOG.md | 2 +- toolchains/node/Cargo.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9646160f..63aef523 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3332,7 +3332,7 @@ dependencies = [ [[package]] name = "node_toolchain" -version = "1.0.3" +version = "1.0.4" dependencies = [ "extism-pdk", "moon_common", diff --git a/toolchains/node/CHANGELOG.md b/toolchains/node/CHANGELOG.md index 2047e588..8a18736a 100644 --- a/toolchains/node/CHANGELOG.md +++ b/toolchains/node/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## Unreleased +## 1.0.4 #### 🚀 Updates diff --git a/toolchains/node/Cargo.toml b/toolchains/node/Cargo.toml index 45b1fc2c..f9d6f77b 100644 --- a/toolchains/node/Cargo.toml +++ b/toolchains/node/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "node_toolchain" -version = "1.0.3" +version = "1.0.4" edition = "2024" description = "Node.js toolchain WASM plugin for moon." authors = ["Miles Johnson"] From 66a5b00696b188e6be52902cd97e485286eea0d5 Mon Sep 17 00:00:00 2001 From: Miles Johnson Date: Wed, 12 Aug 2026 12:07:33 -0700 Subject: [PATCH 65/78] chore: Release --- Cargo.lock | 2 +- toolchains/python-pip/CHANGELOG.md | 2 +- toolchains/python-pip/Cargo.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 63aef523..361f4d57 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4090,7 +4090,7 @@ dependencies = [ [[package]] name = "python_pip_toolchain" -version = "0.1.3" +version = "0.1.4" dependencies = [ "extism-pdk", "moon_config", diff --git a/toolchains/python-pip/CHANGELOG.md b/toolchains/python-pip/CHANGELOG.md index 92526612..7866d3b2 100644 --- a/toolchains/python-pip/CHANGELOG.md +++ b/toolchains/python-pip/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## Unreleased +## 0.1.4 #### 🚀 Updates diff --git a/toolchains/python-pip/Cargo.toml b/toolchains/python-pip/Cargo.toml index 50021c2d..fde3f354 100644 --- a/toolchains/python-pip/Cargo.toml +++ b/toolchains/python-pip/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "python_pip_toolchain" -version = "0.1.3" +version = "0.1.4" edition = "2024" description = "Python pip toolchain WASM plugin for moon." authors = ["Miles Johnson"] From ad3982092206c799b82907414214bac8cdc71a4a Mon Sep 17 00:00:00 2001 From: Miles Johnson Date: Wed, 12 Aug 2026 12:07:47 -0700 Subject: [PATCH 66/78] chore: Release --- Cargo.lock | 2 +- toolchains/python-poetry/CHANGELOG.md | 2 +- toolchains/python-poetry/Cargo.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 361f4d57..8b948f20 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4117,7 +4117,7 @@ dependencies = [ [[package]] name = "python_poetry_toolchain" -version = "0.1.1" +version = "0.1.2" dependencies = [ "extism-pdk", "moon_config", diff --git a/toolchains/python-poetry/CHANGELOG.md b/toolchains/python-poetry/CHANGELOG.md index 6a155f8e..d8eed6fe 100644 --- a/toolchains/python-poetry/CHANGELOG.md +++ b/toolchains/python-poetry/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## Unreleased +## 0.1.2 #### 🚀 Updates diff --git a/toolchains/python-poetry/Cargo.toml b/toolchains/python-poetry/Cargo.toml index 097c3c70..fc01546b 100644 --- a/toolchains/python-poetry/Cargo.toml +++ b/toolchains/python-poetry/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "python_poetry_toolchain" -version = "0.1.1" +version = "0.1.2" edition = "2024" description = "Python Poetry toolchain WASM plugin for moon." authors = ["Miles Johnson"] From d2297a5bec595b6fee1f525ca9bbfde3dd74303a Mon Sep 17 00:00:00 2001 From: Miles Johnson Date: Wed, 12 Aug 2026 12:08:01 -0700 Subject: [PATCH 67/78] chore: Release --- Cargo.lock | 2 +- toolchains/python-uv/CHANGELOG.md | 2 +- toolchains/python-uv/Cargo.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8b948f20..113b959e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4181,7 +4181,7 @@ dependencies = [ [[package]] name = "python_uv_toolchain" -version = "0.1.4" +version = "0.1.5" dependencies = [ "extism-pdk", "moon_config", diff --git a/toolchains/python-uv/CHANGELOG.md b/toolchains/python-uv/CHANGELOG.md index a3d027f9..77c825fa 100644 --- a/toolchains/python-uv/CHANGELOG.md +++ b/toolchains/python-uv/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## Unreleased +## 0.1.5 #### 🚀 Updates diff --git a/toolchains/python-uv/Cargo.toml b/toolchains/python-uv/Cargo.toml index 88ce0f11..c96e0550 100644 --- a/toolchains/python-uv/Cargo.toml +++ b/toolchains/python-uv/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "python_uv_toolchain" -version = "0.1.4" +version = "0.1.5" edition = "2024" description = "Python uv toolchain WASM plugin for moon." authors = ["Miles Johnson"] From 314985d92d39201ed2349e83bd3bfe1c355b9407 Mon Sep 17 00:00:00 2001 From: Miles Johnson Date: Wed, 12 Aug 2026 12:08:15 -0700 Subject: [PATCH 68/78] chore: Release --- Cargo.lock | 2 +- toolchains/rust/CHANGELOG.md | 2 +- toolchains/rust/Cargo.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 113b959e..ca28ecdf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4667,7 +4667,7 @@ dependencies = [ [[package]] name = "rust_toolchain" -version = "1.0.8" +version = "1.0.9" dependencies = [ "cargo-lock", "cargo_toml", diff --git a/toolchains/rust/CHANGELOG.md b/toolchains/rust/CHANGELOG.md index 168ff78f..4b18574f 100644 --- a/toolchains/rust/CHANGELOG.md +++ b/toolchains/rust/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## Unreleased +## 1.0.9 #### 🐞 Fixes diff --git a/toolchains/rust/Cargo.toml b/toolchains/rust/Cargo.toml index 32025e56..87fee9b5 100644 --- a/toolchains/rust/Cargo.toml +++ b/toolchains/rust/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rust_toolchain" -version = "1.0.8" +version = "1.0.9" edition = "2024" description = "Rust toolchain WASM plugin for moon." authors = ["Miles Johnson"] From 2611083dbf17bd19a98ca18d69e88406ce9a49ac Mon Sep 17 00:00:00 2001 From: Miles Johnson Date: Wed, 12 Aug 2026 12:09:41 -0700 Subject: [PATCH 69/78] chore: Release --- Cargo.lock | 2 +- toolchains/javascript/CHANGELOG.md | 2 +- toolchains/javascript/Cargo.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ca28ecdf..a5973e4c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2595,7 +2595,7 @@ dependencies = [ [[package]] name = "javascript_toolchain" -version = "1.2.2" +version = "1.3.0" dependencies = [ "deno_lockfile", "extism-pdk", diff --git a/toolchains/javascript/CHANGELOG.md b/toolchains/javascript/CHANGELOG.md index 6ad0d356..a14e398e 100644 --- a/toolchains/javascript/CHANGELOG.md +++ b/toolchains/javascript/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## Unreleased +## 1.3.0 #### 🚀 Updates diff --git a/toolchains/javascript/Cargo.toml b/toolchains/javascript/Cargo.toml index 3d269354..018dc641 100644 --- a/toolchains/javascript/Cargo.toml +++ b/toolchains/javascript/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "javascript_toolchain" -version = "1.2.2" +version = "1.3.0" edition = "2024" description = "JavaScript toolchain WASM plugin for moon." authors = ["Miles Johnson"] From dd44e43b0e5a9a51e68c1d697b7cb6bc8bc4fcb1 Mon Sep 17 00:00:00 2001 From: Miles Johnson Date: Wed, 12 Aug 2026 12:09:54 -0700 Subject: [PATCH 70/78] chore: Release --- Cargo.lock | 2 +- toolchains/node-depman/CHANGELOG.md | 2 +- toolchains/node-depman/Cargo.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a5973e4c..aa8aecfb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3294,7 +3294,7 @@ dependencies = [ [[package]] name = "node_depman_toolchain" -version = "1.0.4" +version = "1.1.0" dependencies = [ "extism-pdk", "moon_config", diff --git a/toolchains/node-depman/CHANGELOG.md b/toolchains/node-depman/CHANGELOG.md index 0bef25a8..4b0b4bed 100644 --- a/toolchains/node-depman/CHANGELOG.md +++ b/toolchains/node-depman/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## Unreleased +## 1.1.0 #### 🚀 Updates diff --git a/toolchains/node-depman/Cargo.toml b/toolchains/node-depman/Cargo.toml index ebb31858..769cf0da 100644 --- a/toolchains/node-depman/Cargo.toml +++ b/toolchains/node-depman/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "node_depman_toolchain" -version = "1.0.4" +version = "1.1.0" edition = "2024" description = "Node.js dependency managers (npm, pnpm, yarn, nub) toolchain WASM plugin for moon." authors = ["Miles Johnson"] From fc1d77b46793bd242f20c58166e4cdfe824aa8ac Mon Sep 17 00:00:00 2001 From: Miles Johnson Date: Wed, 12 Aug 2026 12:10:08 -0700 Subject: [PATCH 71/78] chore: Release --- Cargo.lock | 2 +- toolchains/python/CHANGELOG.md | 2 +- toolchains/python/Cargo.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index aa8aecfb..b4d78c34 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4144,7 +4144,7 @@ dependencies = [ [[package]] name = "python_toolchain" -version = "0.2.1" +version = "0.3.0" dependencies = [ "extism-pdk", "moon_common", diff --git a/toolchains/python/CHANGELOG.md b/toolchains/python/CHANGELOG.md index 3d4d14d5..7206f760 100644 --- a/toolchains/python/CHANGELOG.md +++ b/toolchains/python/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## Unreleased +## 0.3.0 #### 🚀 Updates diff --git a/toolchains/python/Cargo.toml b/toolchains/python/Cargo.toml index 0699c8ec..6b654b0e 100644 --- a/toolchains/python/Cargo.toml +++ b/toolchains/python/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "python_toolchain" -version = "0.2.1" +version = "0.3.0" edition = "2024" description = "Python toolchain WASM plugin for moon." authors = ["Miles Johnson"] From a123d192d9bbb596edb6b79eedb2be5491db3436 Mon Sep 17 00:00:00 2001 From: Miles Johnson Date: Wed, 12 Aug 2026 21:58:25 -0700 Subject: [PATCH 72/78] new: Support go mod versioning. (#183) --- toolchains/go/CHANGELOG.md | 15 ++ toolchains/go/src/tier2.rs | 85 +++++++++-- .../projects-versioned/arbitrary/go.mod | 1 + .../projects-versioned/consumer/go.mod | 7 + .../projects-versioned/mod/go.mod | 1 + .../projects-versioned/mod/v2/go.mod | 1 + .../projects-versioned/replacer/go.mod | 13 ++ .../projects-versioned/suffixed/v3/go.mod | 1 + toolchains/go/tests/tier2_test.rs | 135 +++++++++++++++++- 9 files changed, 243 insertions(+), 16 deletions(-) create mode 100644 toolchains/go/tests/__fixtures__/projects-versioned/arbitrary/go.mod create mode 100644 toolchains/go/tests/__fixtures__/projects-versioned/consumer/go.mod create mode 100644 toolchains/go/tests/__fixtures__/projects-versioned/mod/go.mod create mode 100644 toolchains/go/tests/__fixtures__/projects-versioned/mod/v2/go.mod create mode 100644 toolchains/go/tests/__fixtures__/projects-versioned/replacer/go.mod create mode 100644 toolchains/go/tests/__fixtures__/projects-versioned/suffixed/v3/go.mod diff --git a/toolchains/go/CHANGELOG.md b/toolchains/go/CHANGELOG.md index 08aad279..c4b5099f 100644 --- a/toolchains/go/CHANGELOG.md +++ b/toolchains/go/CHANGELOG.md @@ -1,5 +1,20 @@ # Changelog +## Unreleased + +#### 🐞 Fixes + +- Fixed project relationships not linking when a `go.mod` is located in a major + version folder (`v2`+) but its `module` directive omits the version suffix. + The suffix is now inferred from the folder name, for both the project's alias + and dependency matching. +- `replace` directives are now honored when linking project relationships. A + replacement pointing to a local directory links to the project at that + location (regardless of module names), while a replacement pointing to + another module no longer creates a relationship. +- Project relationships now reference the dependency's project identifier + instead of its module path alias, aligning with other toolchains. + ## 1.4.6 #### 🐞 Fixes diff --git a/toolchains/go/src/tier2.rs b/toolchains/go/src/tier2.rs index 069a42de..699f5cc8 100644 --- a/toolchains/go/src/tier2.rs +++ b/toolchains/go/src/tier2.rs @@ -1,5 +1,7 @@ use crate::config::GoToolchainConfig; -use crate::go_mod::{GoMod, Module, ModuleDependency, parse_go_mod}; +use crate::go_mod::{ + GoMod, Module, ModuleDependency, ModuleReplacement, Replacement, parse_go_mod, +}; use crate::go_sum::GoSum; use crate::go_work::GoWork; use extism_pdk::*; @@ -89,15 +91,31 @@ pub fn extend_project_graph( // First pass, gather all packages and their manifests let mut packages = BTreeMap::default(); + let mut source_to_id = BTreeMap::default(); for (id, source) in input.project_sources { - let project_root = input.context.workspace_root.join(source); + let project_root = input.context.workspace_root.join(&source); let go_mod_path = project_root.join("go.mod"); let mut manifest = if go_mod_path.exists() { output.input_files.push(go_mod_path.clone()); - parse_go_mod(fs::read_file(&go_mod_path)?)? + let mut manifest = parse_go_mod(fs::read_file(&go_mod_path)?)?; + + // A module in a major version folder (v2+) is imported with the + // version suffix, even when the `module` directive omits it + // https://go.dev/ref/mod#major-version-suffixes + if let Some(version) = source + .rsplit('/') + .next() + .filter(|segment| is_version_segment(segment)) + && !manifest.module.is_empty() + && !is_version_segment(manifest.module.rsplit('/').next().unwrap_or_default()) + { + manifest.module = format!("{}/{version}", manifest.module); + } + + manifest } else { GoMod { // This name isn't correct, but we need something! @@ -124,11 +142,14 @@ pub fn extend_project_graph( } } - packages.insert(manifest.module.clone(), (id, manifest)); + let source = resolve_source_path("", &source).unwrap_or(source); + + source_to_id.insert(source.clone(), id.clone()); + packages.insert(manifest.module.clone(), (id, source, manifest)); } // Second pass, extract packages and their relationships - for (id, manifest) in packages.values() { + for (id, source, manifest) in packages.values() { let mut project_output = ExtendProjectOutput { alias: if manifest.module.is_empty() || manifest.module == id.as_str() { None @@ -139,15 +160,34 @@ pub fn extend_project_graph( }; for dep in &manifest.require { + if dep.indirect { + continue; + } + let dep_module = &dep.module.module_path; - if !dep.indirect - && packages - .get(dep_module) - .is_some_and(|(dep_id, _)| dep_id != id) + // A `replace` directive changes what the required path resolves + // to, so it takes precedence over matching modules directly + let dep_id = match manifest + .replace + .iter() + .find(|replacement| &replacement.module_path == dep_module) + { + // A local directory, so map to the project at that location + Some(ModuleReplacement { + replacement: Replacement::FilePath(path), + .. + }) => resolve_source_path(source, path).and_then(|path| source_to_id.get(&path)), + // Another module, so no longer a local project + Some(_) => None, + None => packages.get(dep_module).map(|(dep_id, _, _)| dep_id), + }; + + if let Some(dep_id) = dep_id + && dep_id != id { project_output.dependencies.push(ProjectDependency { - id: Id::raw(dep_module.clone()), + id: dep_id.to_owned(), scope: if dep.module.version == "internal-test" { DependencyScope::Development } else { @@ -471,6 +511,31 @@ fn get_base_module(module: &str) -> String { base } +// Lexically resolve a relative path (`./`, `../`) against a workspace +// relative source, returning `None` when it escapes the workspace +fn resolve_source_path(base: &str, path: &str) -> Option { + if path.starts_with('/') { + return None; + } + + let mut segments = base + .split('/') + .filter(|segment| !segment.is_empty() && *segment != ".") + .collect::>(); + + for segment in path.split('/') { + match segment { + "" | "." => {} + ".." => { + segments.pop()?; + } + _ => segments.push(segment), + } + } + + Some(segments.join("/")) +} + // A major version suffix segment: v2, v3, etc, but never v0 or v1 // https://go.dev/ref/mod#major-version-suffixes fn is_version_segment(segment: &str) -> bool { diff --git a/toolchains/go/tests/__fixtures__/projects-versioned/arbitrary/go.mod b/toolchains/go/tests/__fixtures__/projects-versioned/arbitrary/go.mod new file mode 100644 index 00000000..ce3f30a4 --- /dev/null +++ b/toolchains/go/tests/__fixtures__/projects-versioned/arbitrary/go.mod @@ -0,0 +1 @@ +module example.com/org/whatever diff --git a/toolchains/go/tests/__fixtures__/projects-versioned/consumer/go.mod b/toolchains/go/tests/__fixtures__/projects-versioned/consumer/go.mod new file mode 100644 index 00000000..75e6f7e1 --- /dev/null +++ b/toolchains/go/tests/__fixtures__/projects-versioned/consumer/go.mod @@ -0,0 +1,7 @@ +module example.com/org/consumer + +require ( + example.com/org/mod v1.0.0 + example.com/org/mod/v2 v2.0.0 + example.com/org/suffixed/v3 v3.0.0 +) diff --git a/toolchains/go/tests/__fixtures__/projects-versioned/mod/go.mod b/toolchains/go/tests/__fixtures__/projects-versioned/mod/go.mod new file mode 100644 index 00000000..705246cd --- /dev/null +++ b/toolchains/go/tests/__fixtures__/projects-versioned/mod/go.mod @@ -0,0 +1 @@ +module example.com/org/mod diff --git a/toolchains/go/tests/__fixtures__/projects-versioned/mod/v2/go.mod b/toolchains/go/tests/__fixtures__/projects-versioned/mod/v2/go.mod new file mode 100644 index 00000000..705246cd --- /dev/null +++ b/toolchains/go/tests/__fixtures__/projects-versioned/mod/v2/go.mod @@ -0,0 +1 @@ +module example.com/org/mod diff --git a/toolchains/go/tests/__fixtures__/projects-versioned/replacer/go.mod b/toolchains/go/tests/__fixtures__/projects-versioned/replacer/go.mod new file mode 100644 index 00000000..67e3ced0 --- /dev/null +++ b/toolchains/go/tests/__fixtures__/projects-versioned/replacer/go.mod @@ -0,0 +1,13 @@ +module example.com/org/replacer + +require ( + example.com/org/renamed v1.0.0 + example.com/org/mod v1.0.0 + example.com/org/outside v1.0.0 +) + +replace example.com/org/renamed => ../arbitrary + +replace example.com/org/mod => example.com/external/mod v1.0.0 + +replace example.com/org/outside => ../../outside diff --git a/toolchains/go/tests/__fixtures__/projects-versioned/suffixed/v3/go.mod b/toolchains/go/tests/__fixtures__/projects-versioned/suffixed/v3/go.mod new file mode 100644 index 00000000..63b6c07b --- /dev/null +++ b/toolchains/go/tests/__fixtures__/projects-versioned/suffixed/v3/go.mod @@ -0,0 +1 @@ +module example.com/org/suffixed/v3 diff --git a/toolchains/go/tests/tier2_test.rs b/toolchains/go/tests/tier2_test.rs index 104e5ba0..9dd8ec77 100644 --- a/toolchains/go/tests/tier2_test.rs +++ b/toolchains/go/tests/tier2_test.rs @@ -47,12 +47,12 @@ mod go_toolchain_tier2 { alias: Some("example.com/org/c".into()), dependencies: vec![ ProjectDependency { - id: Id::raw("example.com/org/a"), + id: Id::raw("a"), scope: DependencyScope::Production, via: Some("module example.com/org/a".into()), }, ProjectDependency { - id: Id::raw("example.com/org/b"), + id: Id::raw("b"), scope: DependencyScope::Production, via: Some("module example.com/org/b".into()), } @@ -116,6 +116,129 @@ mod go_toolchain_tier2 { assert!(output.input_files.is_empty()); } + #[tokio::test(flavor = "multi_thread")] + async fn appends_major_version_folder_to_module() { + let sandbox = create_moon_sandbox("projects-versioned"); + let plugin = sandbox.create_toolchain("go").await; + + let mut input = ExtendProjectGraphInput::default(); + input + .project_sources + .insert(Id::raw("consumer"), "consumer".into()); + input.project_sources.insert(Id::raw("mod"), "mod".into()); + input + .project_sources + .insert(Id::raw("mod-v2"), "mod/v2".into()); + input + .project_sources + .insert(Id::raw("suffixed"), "suffixed/v3".into()); + + let output = plugin.extend_project_graph(input).await; + + assert_eq!( + output.extended_projects, + BTreeMap::from_iter([ + ( + Id::raw("consumer"), + ExtendProjectOutput { + alias: Some("example.com/org/consumer".into()), + dependencies: vec![ + ProjectDependency { + id: Id::raw("mod"), + scope: DependencyScope::Production, + via: Some("module example.com/org/mod".into()), + }, + ProjectDependency { + id: Id::raw("mod-v2"), + scope: DependencyScope::Production, + via: Some("module example.com/org/mod/v2".into()), + }, + ProjectDependency { + id: Id::raw("suffixed"), + scope: DependencyScope::Production, + via: Some("module example.com/org/suffixed/v3".into()), + } + ], + ..Default::default() + } + ), + ( + Id::raw("mod"), + ExtendProjectOutput { + alias: Some("example.com/org/mod".into()), + ..Default::default() + } + ), + ( + Id::raw("mod-v2"), + ExtendProjectOutput { + alias: Some("example.com/org/mod/v2".into()), + ..Default::default() + } + ), + ( + Id::raw("suffixed"), + ExtendProjectOutput { + alias: Some("example.com/org/suffixed/v3".into()), + ..Default::default() + } + ), + ]) + ); + } + + #[tokio::test(flavor = "multi_thread")] + async fn resolves_deps_through_replace_directives() { + let sandbox = create_moon_sandbox("projects-versioned"); + let plugin = sandbox.create_toolchain("go").await; + + let mut input = ExtendProjectGraphInput::default(); + input + .project_sources + .insert(Id::raw("arbitrary"), "arbitrary".into()); + input.project_sources.insert(Id::raw("mod"), "mod".into()); + input + .project_sources + .insert(Id::raw("replacer"), "replacer".into()); + + let output = plugin.extend_project_graph(input).await; + + assert_eq!( + output.extended_projects, + BTreeMap::from_iter([ + ( + Id::raw("arbitrary"), + ExtendProjectOutput { + alias: Some("example.com/org/whatever".into()), + ..Default::default() + } + ), + ( + Id::raw("mod"), + ExtendProjectOutput { + alias: Some("example.com/org/mod".into()), + ..Default::default() + } + ), + ( + Id::raw("replacer"), + ExtendProjectOutput { + alias: Some("example.com/org/replacer".into()), + // The `mod` require is replaced with an external + // module, and `outside` escapes the workspace, so + // neither creates a relationship + dependencies: vec![ProjectDependency { + id: Id::raw("arbitrary"), + scope: DependencyScope::Production, + via: Some("module example.com/org/renamed".into()), + }], + ..Default::default() + } + ), + ]) + ); + } + mod go_list { use super::*; @@ -157,12 +280,12 @@ mod go_toolchain_tier2 { alias: Some("example.com/org/c".into()), dependencies: vec![ ProjectDependency { - id: Id::raw("example.com/org/a"), + id: Id::raw("a"), scope: DependencyScope::Production, via: Some("module example.com/org/a".into()), }, ProjectDependency { - id: Id::raw("example.com/org/b"), + id: Id::raw("b"), scope: DependencyScope::Production, via: Some("module example.com/org/b".into()), } @@ -256,7 +379,7 @@ mod go_toolchain_tier2 { Some(&ExtendProjectOutput { alias: Some("example.com/org/d".into()), dependencies: vec![ProjectDependency { - id: Id::raw("example.com/org/a"), + id: Id::raw("a"), scope: DependencyScope::Production, via: Some("module example.com/org/a".into()), }], @@ -288,7 +411,7 @@ mod go_toolchain_tier2 { Some(&ExtendProjectOutput { alias: Some("example.com/org/e".into()), dependencies: vec![ProjectDependency { - id: Id::raw("example.com/org/a"), + id: Id::raw("a"), scope: DependencyScope::Production, via: Some("module example.com/org/a".into()), }], From 31558a7e7d9b8dd773e4645d199c86a8d9f26f07 Mon Sep 17 00:00:00 2001 From: Miles Johnson Date: Wed, 12 Aug 2026 21:59:02 -0700 Subject: [PATCH 73/78] chore: Release --- Cargo.lock | 2 +- toolchains/go/CHANGELOG.md | 2 +- toolchains/go/Cargo.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b4d78c34..4e8b8f3f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1984,7 +1984,7 @@ dependencies = [ [[package]] name = "go_toolchain" -version = "1.4.6" +version = "1.4.7" dependencies = [ "extism-pdk", "go_tool", diff --git a/toolchains/go/CHANGELOG.md b/toolchains/go/CHANGELOG.md index c4b5099f..c62585b0 100644 --- a/toolchains/go/CHANGELOG.md +++ b/toolchains/go/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## Unreleased +## 1.4.7 #### 🐞 Fixes diff --git a/toolchains/go/Cargo.toml b/toolchains/go/Cargo.toml index 6e5bf2cc..6c629d43 100644 --- a/toolchains/go/Cargo.toml +++ b/toolchains/go/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "go_toolchain" -version = "1.4.6" +version = "1.4.7" edition = "2024" description = "Go toolchain WASM plugin for moon." authors = ["Miles Johnson"] From c31826b125e2f16b2813ec9fc5b1ff205b91552e Mon Sep 17 00:00:00 2001 From: jonpoole-fluidstack Date: Fri, 14 Aug 2026 16:50:35 +0100 Subject: [PATCH 74/78] feat(go): Use go list to find edges based on import paths from projects (#182) Co-authored-by: Claude Fable 5 --- toolchains/go/CHANGELOG.md | 9 + toolchains/go/src/lib.rs | 2 + toolchains/go/src/package_graph.rs | 483 ++++++++++++++++++ toolchains/go/src/tier2.rs | 240 +-------- .../projects-single-module/apps/a/main.go | 7 + .../apps/a/tool/tool.go | 3 + .../projects-single-module/go.mod | 3 + .../projects-single-module/libs/b/lib.go | 3 + .../consumer/go.mod | 1 + .../consumer/lib.go | 11 + .../projects-workspace-versioned/go.work | 7 + .../projects-workspace-versioned/mod/go.mod | 1 + .../projects-workspace-versioned/mod/lib.go | 3 + .../mod/v2/go.mod | 1 + .../mod/v2/lib.go | 3 + toolchains/go/tests/tier2_test.rs | 172 +++++-- 16 files changed, 693 insertions(+), 256 deletions(-) create mode 100644 toolchains/go/src/package_graph.rs create mode 100644 toolchains/go/tests/__fixtures__/projects-single-module/apps/a/main.go create mode 100644 toolchains/go/tests/__fixtures__/projects-single-module/apps/a/tool/tool.go create mode 100644 toolchains/go/tests/__fixtures__/projects-single-module/go.mod create mode 100644 toolchains/go/tests/__fixtures__/projects-single-module/libs/b/lib.go create mode 100644 toolchains/go/tests/__fixtures__/projects-workspace-versioned/consumer/go.mod create mode 100644 toolchains/go/tests/__fixtures__/projects-workspace-versioned/consumer/lib.go create mode 100644 toolchains/go/tests/__fixtures__/projects-workspace-versioned/go.work create mode 100644 toolchains/go/tests/__fixtures__/projects-workspace-versioned/mod/go.mod create mode 100644 toolchains/go/tests/__fixtures__/projects-workspace-versioned/mod/lib.go create mode 100644 toolchains/go/tests/__fixtures__/projects-workspace-versioned/mod/v2/go.mod create mode 100644 toolchains/go/tests/__fixtures__/projects-workspace-versioned/mod/v2/lib.go diff --git a/toolchains/go/CHANGELOG.md b/toolchains/go/CHANGELOG.md index c62585b0..dcaa9049 100644 --- a/toolchains/go/CHANGELOG.md +++ b/toolchains/go/CHANGELOG.md @@ -1,5 +1,14 @@ # Changelog +## Unreleased + +#### 🚀 Updates + +- Reworked relationship inference to match package import paths instead of module paths. Each project now resolves a canonical import path (nearest `go.mod` module path plus the project's relative directory), and `go list -deps` results are matched against those by longest prefix. This makes relationships resolvable in repositories that share a single `go.mod` across all projects. +- Sibling modules required by version without a `go.work` no longer create project relationships, since those builds consume the published module rather than the local source. When the `go` binary is unavailable, projects with their own `go.mod` under a workspace `go.work` fall back to resolving relationships from their direct requires. +- `replace` directives keep their meaning in the new model: a require replaced by a local directory always links to the project at that location (it consumes local source even without a `go.work`), while a require replaced by another module never links. +- Imports within a project's own import path are treated as ownership rather than dependencies. `go list -deps ./...` enumerates packages belonging to projects nested inside the scanned project, which previously inferred an edge from the parent to every nested child — forming a cycle whenever a child declared `dependsOn` on its parent. + ## 1.4.7 #### 🐞 Fixes diff --git a/toolchains/go/src/lib.rs b/toolchains/go/src/lib.rs index 0f2d4748..6a349766 100644 --- a/toolchains/go/src/lib.rs +++ b/toolchains/go/src/lib.rs @@ -3,6 +3,8 @@ pub mod go_mod; pub mod go_sum; pub mod go_work; +#[cfg(feature = "wasm")] +mod package_graph; #[cfg(feature = "wasm")] mod tier1; #[cfg(feature = "wasm")] diff --git a/toolchains/go/src/package_graph.rs b/toolchains/go/src/package_graph.rs new file mode 100644 index 00000000..5c64ddbe --- /dev/null +++ b/toolchains/go/src/package_graph.rs @@ -0,0 +1,483 @@ +use crate::config::GoToolchainConfig; +use crate::go_mod::{GoMod, ModuleReplacement, Replacement, parse_go_mod}; +use moon_config::DependencyScope; +use moon_pdk::exec; +use moon_pdk_api::*; +use starbase_utils::fs; +use std::collections::{BTreeMap, BTreeSet}; + +// The `go list` half of the package graph: every package a directory's +// packages depend on, as canonical import paths. +fn execute_go_list(dir: &VirtualPath, packages: &[String], test: bool) -> AnyResult> { + let mut args = vec![ + "list", + "-deps", + "-f", + "{{if .Module}}{{.ImportPath}}{{end}}", + ]; + + if test { + args.push("-test"); + } + + // Scan all packages recursively by default so that dependencies imported + // only from subdirectories (internal/, pkg/, ...) are also inferred. + if packages.is_empty() { + args.push("./..."); + } else { + for package in packages { + args.push(package.as_str()); + } + } + + let result = exec(ExecCommandInput::pipe("go", args).cwd(dir.to_owned()))?; + + if result.exit_code != 0 { + return Ok(vec![]); + } + + Ok(result + .stdout + .lines() + .filter_map(|line| { + // Test binary pseudo-packages render as `pkg [pkg.test]`; + // only the real package path participates in matching. + let import_path = line.trim().split(' ').next().unwrap_or_default(); + + (!import_path.is_empty()).then(|| import_path.to_owned()) + }) + .collect()) +} + +// Whether `import_path` is the package at `prefix` itself, or nested beneath +// it. +fn import_within(import_path: &str, prefix: &str) -> bool { + import_path == prefix + || import_path + .strip_prefix(prefix) + .is_some_and(|rest| rest.starts_with('/')) +} + +// Workspace-relative dirs that may own a directory's `go.mod`: the dir +// itself, then each ancestor up to the workspace root (""). +fn module_dir_candidates(source: &str) -> Vec<&str> { + let mut candidates = vec![]; + let mut dir = source; + + while !dir.is_empty() { + candidates.push(dir); + dir = dir.rfind('/').map_or("", |index| &dir[..index]); + } + + candidates.push(""); + candidates +} + +// Resolves workspace directories to the import paths their packages live +// under, caching each `go.mod` it parses along the way. A `None` cache entry +// memoizes "no usable `go.mod` here", so ancestors shared between +// directories only hit the disk once. +struct ModuleResolver { + workspace_root: VirtualPath, + go_mods: BTreeMap>, +} + +impl ModuleResolver { + fn new(workspace_root: VirtualPath) -> Self { + Self { + workspace_root, + go_mods: BTreeMap::default(), + } + } + + // The canonical import path of a workspace-relative dir: the module path + // of the nearest `go.mod` at or above it (bounded by the workspace + // root), joined with the dir's path relative to that module root. + // Returns the owning dir alongside so callers can key back into the + // cache. + fn import_path(&mut self, source: &str) -> AnyResult> { + for dir in module_dir_candidates(source) { + if !self.go_mods.contains_key(dir) { + let go_mod_path = self.go_mod_path(dir); + + let manifest = if go_mod_path.exists() { + Some(parse_go_mod(fs::read_file(&go_mod_path)?)?) + .filter(|manifest| !manifest.module.is_empty()) + .map(|mut manifest| { + // A module in a major version folder (v2+) is + // imported with the version suffix, even when the + // `module` directive omits it + // https://go.dev/ref/mod#major-version-suffixes + if let Some(version) = dir + .rsplit('/') + .next() + .filter(|segment| is_version_segment(segment)) + && !is_version_segment( + manifest.module.rsplit('/').next().unwrap_or_default(), + ) + { + manifest.module = format!("{}/{version}", manifest.module); + } + + manifest + }) + } else { + None + }; + + self.go_mods.insert(dir.to_owned(), manifest); + } + + if let Some(manifest) = self.go_mods.get(dir).and_then(|entry| entry.as_ref()) { + let relative = source[dir.len()..].trim_start_matches('/'); + + let import_path = if relative.is_empty() { + manifest.module.clone() + } else { + format!("{}/{relative}", manifest.module) + }; + + return Ok(Some((dir.to_owned(), import_path))); + } + } + + Ok(None) + } + + // The parsed manifest owned by a dir previously resolved through + // `import_path`, with any major version suffix already applied to its + // module path. + fn manifest(&self, dir: &str) -> Option<&GoMod> { + self.go_mods.get(dir).and_then(|entry| entry.as_ref()) + } + + fn go_mod_path(&self, dir: &str) -> VirtualPath { + if dir.is_empty() { + self.workspace_root.join("go.mod") + } else { + self.workspace_root.join(dir).join("go.mod") + } + } +} + +pub struct GoProject { + pub id: Id, + /// Module path as declared when the project owns its `go.mod` + pub alias: Option, + root: VirtualPath, + import_path: Option, + /// Direct module requires from the project's own `go.mod` + requires: Vec, +} + +struct GoRequire { + module_path: String, + target: GoRequireTarget, +} + +// What a `require` actually resolves to once `replace` directives are +// applied, which determines whether it can link to a local project. +enum GoRequireTarget { + /// Required by version; only links when the environment wires the + /// module to local source (a `go.work` workspace) + Module, + /// Replaced by a local directory (workspace-relative), so it always + /// consumes local source + LocalSource(String), + /// Replaced by another module, or a path outside the workspace, so it + /// can never be a local project + External, +} + +// All the state relationship inference works from: the resolved projects, +// the import-path prefix map they resolve against, and the module resolver +// used to build both. +pub struct GoPackageGraph { + workspace_root: VirtualPath, + config: GoToolchainConfig, + go_exists: bool, + resolver: ModuleResolver, + projects: Vec, + /// Project lookup by workspace-relative source dir, for `replace` + /// directives that point at local directories + source_to_id: BTreeMap, + /// Import path each project resolves to, matched by prefix + package_prefixes: Vec<(String, Id)>, + /// The `go.mod` files backing the resolved import paths + input_files: Vec, +} + +impl GoPackageGraph { + pub fn new(workspace_root: VirtualPath, config: GoToolchainConfig, go_exists: bool) -> Self { + Self { + resolver: ModuleResolver::new(workspace_root.clone()), + workspace_root, + config, + go_exists, + projects: vec![], + source_to_id: BTreeMap::default(), + package_prefixes: vec![], + input_files: vec![], + } + } + + // First pass: resolve every project to its import path and manifest, and + // build the prefix map that imports are resolved against. + pub fn load_projects(&mut self, sources: BTreeMap) -> AnyResult<()> { + for (id, source) in sources { + let root = self.workspace_root.join(&source); + let source = if source == "." { "" } else { source.as_str() }; + + self.source_to_id.insert(source.to_owned(), id.clone()); + + let mut project = GoProject { + id, + root, + alias: None, + import_path: None, + requires: vec![], + }; + + if let Some((mod_dir, import_path)) = self.resolver.import_path(source)? { + let go_mod_path = self.resolver.go_mod_path(&mod_dir); + + if !self.input_files.contains(&go_mod_path) { + self.input_files.push(go_mod_path); + } + + // An ancestor's requires describe the whole module, so they + // only participate for the project that owns the `go.mod` + if mod_dir == source + && let Some(manifest) = self.resolver.manifest(&mod_dir) + { + if manifest.module != project.id.as_str() { + project.alias = Some(manifest.module.clone()); + } + + project.requires = manifest + .require + .iter() + .filter(|dep| !dep.indirect) + .map(|dep| { + let module_path = dep.module.module_path.clone(); + + // A `replace` directive changes what the require + // resolves to, so it takes precedence over + // matching import paths + let target = match manifest + .replace + .iter() + .find(|replacement| replacement.module_path == module_path) + { + Some(ModuleReplacement { + replacement: Replacement::FilePath(path), + .. + }) => resolve_source_path(source, path).map_or( + GoRequireTarget::External, + GoRequireTarget::LocalSource, + ), + Some(_) => GoRequireTarget::External, + None => GoRequireTarget::Module, + }; + + GoRequire { + module_path, + target, + } + }) + .collect(); + } + + project.import_path = Some(import_path); + } + + self.projects.push(project); + } + + self.package_prefixes = self + .projects + .iter() + .filter_map(|project| { + project + .import_path + .clone() + .map(|path| (path, project.id.clone())) + }) + .collect(); + + Ok(()) + } + + pub fn projects(&self) -> &[GoProject] { + &self.projects + } + + pub fn into_input_files(self) -> Vec { + self.input_files + } + + // Resolves an import path to the project whose import path prefixes it. + // The longest match wins, so the module root can't shadow projects + // nested beneath it. + fn resolve_import(&self, import_path: &str) -> Option<&Id> { + self.package_prefixes + .iter() + .filter(|(prefix, _)| import_within(import_path, prefix)) + .max_by_key(|(prefix, _)| prefix.len()) + .map(|(_, id)| id) + } + + // Second pass: infer one project's dependencies, picking the mechanism + // the environment supports. + pub fn project_dependencies(&self, project: &GoProject) -> AnyResult> { + let mut dependencies = vec![]; + + // A project without an import path may still sit under a `go.work`, + // where `go list` resolves imports in workspace mode + if self.go_exists + && (project.import_path.is_some() || project.root.join("go.work").exists()) + { + dependencies = self.dependencies_from_go_list(project)?; + } + + // Monorepos using go.work can be resolved purely off the modfile: the + // workspace wires required sibling modules to their local source, so + // a project's own requires reflect real local relationships. That + // only matters when `go` isn't around to resolve them properly, while + // requires replaced by a local directory always consume local source. + let include_unreplaced = !self.go_exists + && self.workspace_root.join("go.work").exists() + && project.root.join("go.mod").exists(); + + for dependency in self.dependencies_from_modfile(project, include_unreplaced) { + if !dependencies.iter().any(|dep| dep.id == dependency.id) { + dependencies.push(dependency); + } + } + + Ok(dependencies) + } + + fn dependencies_from_modfile( + &self, + project: &GoProject, + include_unreplaced: bool, + ) -> Vec { + let mut dependencies = vec![]; + let mut seen = BTreeSet::new(); + + for require in &project.requires { + let dep_id = match &require.target { + GoRequireTarget::LocalSource(path) => self.source_to_id.get(path), + GoRequireTarget::External => None, + GoRequireTarget::Module => include_unreplaced + .then(|| self.resolve_import(&require.module_path)) + .flatten(), + }; + + if let Some(dep_id) = dep_id + && dep_id != &project.id + && seen.insert(dep_id) + { + dependencies.push(ProjectDependency { + id: dep_id.to_owned(), + scope: DependencyScope::Production, + via: Some(format!("module {}", require.module_path)), + }); + } + } + + dependencies + } + + fn dependencies_from_go_list(&self, project: &GoProject) -> AnyResult> { + let mut dependencies = vec![]; + let mut seen = BTreeSet::new(); + + for (enabled, test, scope) in [ + ( + self.config.infer_relationships, + false, + DependencyScope::Production, + ), + ( + self.config.infer_relationships_from_tests, + true, + DependencyScope::Development, + ), + ] { + if !enabled { + continue; + } + + let imports = execute_go_list( + &project.root, + &self.config.infer_relationships_packages, + test, + )?; + + for import_path in imports { + // `./...` also enumerates packages belonging to projects + // nested inside this one; anything within the project's own + // import path is ownership, not an import + if let Some(own) = project.import_path.as_deref() + && import_within(&import_path, own) + { + continue; + } + + if let Some(dep_id) = self.resolve_import(&import_path) + && dep_id != &project.id + && seen.insert(dep_id) + { + dependencies.push(ProjectDependency { + id: dep_id.to_owned(), + scope, + via: Some(format!("package {import_path}")), + }); + } + } + } + + Ok(dependencies) + } +} + +// Lexically resolve a relative path (`./`, `../`) against a workspace +// relative source, returning `None` when it escapes the workspace +fn resolve_source_path(base: &str, path: &str) -> Option { + if path.starts_with('/') { + return None; + } + + let mut segments = base + .split('/') + .filter(|segment| !segment.is_empty() && *segment != ".") + .collect::>(); + + for segment in path.split('/') { + match segment { + "" | "." => {} + ".." => { + segments.pop()?; + } + _ => segments.push(segment), + } + } + + Some(segments.join("/")) +} + +// A major version suffix segment: v2, v3, etc, but never v0 or v1 +// https://go.dev/ref/mod#major-version-suffixes +pub fn is_version_segment(segment: &str) -> bool { + match segment.strip_prefix('v') { + Some(digits) => { + !digits.is_empty() + && !digits.starts_with('0') + && digits != "1" + && digits.chars().all(|c| c.is_ascii_digit()) + } + None => false, + } +} diff --git a/toolchains/go/src/tier2.rs b/toolchains/go/src/tier2.rs index 699f5cc8..aeab47f4 100644 --- a/toolchains/go/src/tier2.rs +++ b/toolchains/go/src/tier2.rs @@ -1,13 +1,12 @@ use crate::config::GoToolchainConfig; -use crate::go_mod::{ - GoMod, Module, ModuleDependency, ModuleReplacement, Replacement, parse_go_mod, -}; +use crate::go_mod::parse_go_mod; use crate::go_sum::GoSum; use crate::go_work::GoWork; +use crate::package_graph::{GoPackageGraph, is_version_segment}; use extism_pdk::*; -use moon_config::{BinEntry, DependencyScope}; +use moon_config::BinEntry; use moon_pdk::{ - VirtualPathExt, command_exists, exec, get_host_env_var, get_host_environment, locate_root, + VirtualPathExt, command_exists, get_host_env_var, get_host_environment, locate_root, parse_toolchain_config_schema, }; use moon_pdk_api::*; @@ -15,199 +14,41 @@ use starbase_utils::fs; use std::collections::BTreeMap; use std::path::PathBuf; -fn is_go_project(dir: &VirtualPath) -> bool { - dir.join("go.mod").exists() - || dir.join("go.sum").exists() - || dir.join("go.work").exists() - || dir.join("main.go").exists() -} - -fn execute_go_list( - dir: &VirtualPath, - packages: &[String], - test: bool, -) -> AnyResult> { - let mut args = vec![ - "list", - "-deps", - "-f", - "{{if .Module}}{{.Module.Path}}{{end}}", - ]; - - if test { - args.push("-test"); - } - - // Scan all packages recursively by default so that dependencies imported - // only from subdirectories (internal/, pkg/, ...) are also inferred. - if packages.is_empty() { - args.push("./..."); - } else { - for package in packages { - args.push(package.as_str()); - } - } - - let result = exec(ExecCommandInput::pipe("go", args).cwd(dir.to_owned()))?; - - if result.exit_code != 0 { - return Ok(vec![]); - } - - Ok(result - .stdout - .lines() - .flat_map(|line| { - let line = line.trim(); - - if line.is_empty() { - None - } else { - Some(ModuleDependency { - module: Module { - module_path: line.into(), - // This is a hack for our use case! - version: if test { - "internal-test".into() - } else { - "".into() - }, - }, - indirect: false, - }) - } - }) - .collect()) -} - #[plugin_fn] pub fn extend_project_graph( Json(input): Json, ) -> FnResult> { - let mut output = ExtendProjectGraphOutput::default(); let config = parse_toolchain_config_schema::(input.toolchain_config)?; let env = get_host_environment()?; - let go_exists = command_exists(env, "go"); - - // First pass, gather all packages and their manifests - let mut packages = BTreeMap::default(); - let mut source_to_id = BTreeMap::default(); - - for (id, source) in input.project_sources { - let project_root = input.context.workspace_root.join(&source); - let go_mod_path = project_root.join("go.mod"); - - let mut manifest = if go_mod_path.exists() { - output.input_files.push(go_mod_path.clone()); - - let mut manifest = parse_go_mod(fs::read_file(&go_mod_path)?)?; - - // A module in a major version folder (v2+) is imported with the - // version suffix, even when the `module` directive omits it - // https://go.dev/ref/mod#major-version-suffixes - if let Some(version) = source - .rsplit('/') - .next() - .filter(|segment| is_version_segment(segment)) - && !manifest.module.is_empty() - && !is_version_segment(manifest.module.rsplit('/').next().unwrap_or_default()) - { - manifest.module = format!("{}/{version}", manifest.module); - } - - manifest - } else { - GoMod { - // This name isn't correct, but we need something! - module: id.to_string(), - ..Default::default() - } - }; - if go_exists && is_go_project(&project_root) { - if config.infer_relationships { - manifest.require.extend(execute_go_list( - &project_root, - &config.infer_relationships_packages, - false, - )?); - } - - if config.infer_relationships_from_tests { - manifest.require.extend(execute_go_list( - &project_root, - &config.infer_relationships_packages, - true, - )?); - } - } + let mut graph = GoPackageGraph::new( + input.context.workspace_root, + config, + command_exists(env, "go"), + ); - let source = resolve_source_path("", &source).unwrap_or(source); + // First pass through, we figure out what projects we have and what their root import path is + graph.load_projects(input.project_sources)?; - source_to_id.insert(source.clone(), id.clone()); - packages.insert(manifest.module.clone(), (id, source, manifest)); - } + let mut output = ExtendProjectGraphOutput::default(); - // Second pass, extract packages and their relationships - for (id, source, manifest) in packages.values() { - let mut project_output = ExtendProjectOutput { - alias: if manifest.module.is_empty() || manifest.module == id.as_str() { - None - } else { - Some(manifest.module.clone()) - }, + // On the second pass, we work through all the projects and resolve their dependencies + for project in graph.projects() { + let project_output = ExtendProjectOutput { + alias: project.alias.clone(), + dependencies: graph.project_dependencies(project)?, ..Default::default() }; - for dep in &manifest.require { - if dep.indirect { - continue; - } - - let dep_module = &dep.module.module_path; - - // A `replace` directive changes what the required path resolves - // to, so it takes precedence over matching modules directly - let dep_id = match manifest - .replace - .iter() - .find(|replacement| &replacement.module_path == dep_module) - { - // A local directory, so map to the project at that location - Some(ModuleReplacement { - replacement: Replacement::FilePath(path), - .. - }) => resolve_source_path(source, path).and_then(|path| source_to_id.get(&path)), - // Another module, so no longer a local project - Some(_) => None, - None => packages.get(dep_module).map(|(dep_id, _, _)| dep_id), - }; - - if let Some(dep_id) = dep_id - && dep_id != id - { - project_output.dependencies.push(ProjectDependency { - id: dep_id.to_owned(), - scope: if dep.module.version == "internal-test" { - DependencyScope::Development - } else { - DependencyScope::Production - }, - via: Some(format!("module {}", dep_module)), - }); - } - } - - if project_output.alias.is_some() - || !project_output.dependencies.is_empty() - || !project_output.tasks.is_empty() - { + if project_output.alias.is_some() || !project_output.dependencies.is_empty() { output .extended_projects - .insert(id.to_owned(), project_output); + .insert(project.id.to_owned(), project_output); } } + output.input_files = graph.into_input_files(); + Ok(Json(output)) } @@ -511,45 +352,6 @@ fn get_base_module(module: &str) -> String { base } -// Lexically resolve a relative path (`./`, `../`) against a workspace -// relative source, returning `None` when it escapes the workspace -fn resolve_source_path(base: &str, path: &str) -> Option { - if path.starts_with('/') { - return None; - } - - let mut segments = base - .split('/') - .filter(|segment| !segment.is_empty() && *segment != ".") - .collect::>(); - - for segment in path.split('/') { - match segment { - "" | "." => {} - ".." => { - segments.pop()?; - } - _ => segments.push(segment), - } - } - - Some(segments.join("/")) -} - -// A major version suffix segment: v2, v3, etc, but never v0 or v1 -// https://go.dev/ref/mod#major-version-suffixes -fn is_version_segment(segment: &str) -> bool { - match segment.strip_prefix('v') { - Some(digits) => { - !digits.is_empty() - && !digits.starts_with('0') - && digits != "1" - && digits.chars().all(|c| c.is_ascii_digit()) - } - None => false, - } -} - // The executable is named after the last segment of the module path, // excluding a major version suffix: `github.com/foo/bar/v2` -> `bar` fn get_bin_name(module: &str) -> &str { diff --git a/toolchains/go/tests/__fixtures__/projects-single-module/apps/a/main.go b/toolchains/go/tests/__fixtures__/projects-single-module/apps/a/main.go new file mode 100644 index 00000000..5a3780f5 --- /dev/null +++ b/toolchains/go/tests/__fixtures__/projects-single-module/apps/a/main.go @@ -0,0 +1,7 @@ +package main + +import "example.com/org/libs/b" + +func main() { + b.B() +} diff --git a/toolchains/go/tests/__fixtures__/projects-single-module/apps/a/tool/tool.go b/toolchains/go/tests/__fixtures__/projects-single-module/apps/a/tool/tool.go new file mode 100644 index 00000000..6757a538 --- /dev/null +++ b/toolchains/go/tests/__fixtures__/projects-single-module/apps/a/tool/tool.go @@ -0,0 +1,3 @@ +package tool + +func Tool() {} diff --git a/toolchains/go/tests/__fixtures__/projects-single-module/go.mod b/toolchains/go/tests/__fixtures__/projects-single-module/go.mod new file mode 100644 index 00000000..9ced3f55 --- /dev/null +++ b/toolchains/go/tests/__fixtures__/projects-single-module/go.mod @@ -0,0 +1,3 @@ +module example.com/org + +go 1.24 diff --git a/toolchains/go/tests/__fixtures__/projects-single-module/libs/b/lib.go b/toolchains/go/tests/__fixtures__/projects-single-module/libs/b/lib.go new file mode 100644 index 00000000..9d2a7e60 --- /dev/null +++ b/toolchains/go/tests/__fixtures__/projects-single-module/libs/b/lib.go @@ -0,0 +1,3 @@ +package b + +func B() {} diff --git a/toolchains/go/tests/__fixtures__/projects-workspace-versioned/consumer/go.mod b/toolchains/go/tests/__fixtures__/projects-workspace-versioned/consumer/go.mod new file mode 100644 index 00000000..625851a0 --- /dev/null +++ b/toolchains/go/tests/__fixtures__/projects-workspace-versioned/consumer/go.mod @@ -0,0 +1 @@ +module example.com/org/consumer diff --git a/toolchains/go/tests/__fixtures__/projects-workspace-versioned/consumer/lib.go b/toolchains/go/tests/__fixtures__/projects-workspace-versioned/consumer/lib.go new file mode 100644 index 00000000..5d4b3ee5 --- /dev/null +++ b/toolchains/go/tests/__fixtures__/projects-workspace-versioned/consumer/lib.go @@ -0,0 +1,11 @@ +package consumer + +import ( + modv1 "example.com/org/mod" + modv2 "example.com/org/mod/v2" +) + +func Consume() { + modv1.Old() + modv2.New() +} diff --git a/toolchains/go/tests/__fixtures__/projects-workspace-versioned/go.work b/toolchains/go/tests/__fixtures__/projects-workspace-versioned/go.work new file mode 100644 index 00000000..6d065e4e --- /dev/null +++ b/toolchains/go/tests/__fixtures__/projects-workspace-versioned/go.work @@ -0,0 +1,7 @@ +go 1.24 + +use ( + ./consumer + ./mod + ./mod/v2 +) diff --git a/toolchains/go/tests/__fixtures__/projects-workspace-versioned/mod/go.mod b/toolchains/go/tests/__fixtures__/projects-workspace-versioned/mod/go.mod new file mode 100644 index 00000000..705246cd --- /dev/null +++ b/toolchains/go/tests/__fixtures__/projects-workspace-versioned/mod/go.mod @@ -0,0 +1 @@ +module example.com/org/mod diff --git a/toolchains/go/tests/__fixtures__/projects-workspace-versioned/mod/lib.go b/toolchains/go/tests/__fixtures__/projects-workspace-versioned/mod/lib.go new file mode 100644 index 00000000..4c011136 --- /dev/null +++ b/toolchains/go/tests/__fixtures__/projects-workspace-versioned/mod/lib.go @@ -0,0 +1,3 @@ +package mod + +func Old() {} diff --git a/toolchains/go/tests/__fixtures__/projects-workspace-versioned/mod/v2/go.mod b/toolchains/go/tests/__fixtures__/projects-workspace-versioned/mod/v2/go.mod new file mode 100644 index 00000000..9d858339 --- /dev/null +++ b/toolchains/go/tests/__fixtures__/projects-workspace-versioned/mod/v2/go.mod @@ -0,0 +1 @@ +module example.com/org/mod/v2 diff --git a/toolchains/go/tests/__fixtures__/projects-workspace-versioned/mod/v2/lib.go b/toolchains/go/tests/__fixtures__/projects-workspace-versioned/mod/v2/lib.go new file mode 100644 index 00000000..683a5aaf --- /dev/null +++ b/toolchains/go/tests/__fixtures__/projects-workspace-versioned/mod/v2/lib.go @@ -0,0 +1,3 @@ +package mod + +func New() {} diff --git a/toolchains/go/tests/tier2_test.rs b/toolchains/go/tests/tier2_test.rs index 9dd8ec77..6b0569be 100644 --- a/toolchains/go/tests/tier2_test.rs +++ b/toolchains/go/tests/tier2_test.rs @@ -42,21 +42,12 @@ mod go_toolchain_tier2 { } ), ( + // `c` requires the sibling modules in its `go.mod` + // but has no source importing them; a require without + // a real import is not a relationship. Id::raw("c"), ExtendProjectOutput { alias: Some("example.com/org/c".into()), - dependencies: vec![ - ProjectDependency { - id: Id::raw("a"), - scope: DependencyScope::Production, - via: Some("module example.com/org/a".into()), - }, - ProjectDependency { - id: Id::raw("b"), - scope: DependencyScope::Production, - via: Some("module example.com/org/b".into()), - } - ], ..Default::default() } ), @@ -139,26 +130,13 @@ mod go_toolchain_tier2 { output.extended_projects, BTreeMap::from_iter([ ( + // `consumer` requires the versioned modules in its + // `go.mod` but has no source importing them, so no + // relationships are created; the version-suffixed + // aliases below still resolve. Id::raw("consumer"), ExtendProjectOutput { alias: Some("example.com/org/consumer".into()), - dependencies: vec![ - ProjectDependency { - id: Id::raw("mod"), - scope: DependencyScope::Production, - via: Some("module example.com/org/mod".into()), - }, - ProjectDependency { - id: Id::raw("mod-v2"), - scope: DependencyScope::Production, - via: Some("module example.com/org/mod/v2".into()), - }, - ProjectDependency { - id: Id::raw("suffixed"), - scope: DependencyScope::Production, - via: Some("module example.com/org/suffixed/v3".into()), - } - ], ..Default::default() } ), @@ -282,12 +260,12 @@ mod go_toolchain_tier2 { ProjectDependency { id: Id::raw("a"), scope: DependencyScope::Production, - via: Some("module example.com/org/a".into()), + via: Some("package example.com/org/a".into()), }, ProjectDependency { id: Id::raw("b"), scope: DependencyScope::Production, - via: Some("module example.com/org/b".into()), + via: Some("package example.com/org/b".into()), } ], ..Default::default() @@ -306,6 +284,127 @@ mod go_toolchain_tier2 { ); } + #[tokio::test(flavor = "multi_thread")] + async fn distinguishes_major_versioned_modules() { + let sandbox = create_moon_sandbox("projects-workspace-versioned"); + let plugin = sandbox.create_toolchain("go").await; + + let mut input = ExtendProjectGraphInput::default(); + input + .project_sources + .insert(Id::raw("consumer"), "consumer".into()); + input.project_sources.insert(Id::raw("mod"), "mod".into()); + input + .project_sources + .insert(Id::raw("mod-v2"), "mod/v2".into()); + input.toolchain_config = json!({ + "inferRelationships": true + }); + + let output = plugin.extend_project_graph(input).await; + + // `example.com/org/mod` prefixes `example.com/org/mod/v2`, so + // the v2 import must resolve to the v2 project rather than + // collapsing into the v1 module. + assert_eq!( + output.extended_projects.get(&Id::raw("consumer")), + Some(&ExtendProjectOutput { + alias: Some("example.com/org/consumer".into()), + dependencies: vec![ + ProjectDependency { + id: Id::raw("mod"), + scope: DependencyScope::Production, + via: Some("package example.com/org/mod".into()), + }, + ProjectDependency { + id: Id::raw("mod-v2"), + scope: DependencyScope::Production, + via: Some("package example.com/org/mod/v2".into()), + }, + ], + ..Default::default() + }) + ); + + assert_eq!( + output.extended_projects.get(&Id::raw("mod-v2")), + Some(&ExtendProjectOutput { + alias: Some("example.com/org/mod/v2".into()), + ..Default::default() + }) + ); + } + + #[tokio::test(flavor = "multi_thread")] + async fn infers_relations_within_a_single_module() { + let sandbox = create_moon_sandbox("projects-single-module"); + let plugin = sandbox.create_toolchain("go").await; + + let mut input = ExtendProjectGraphInput::default(); + input.project_sources.insert(Id::raw("a"), "apps/a".into()); + input.project_sources.insert(Id::raw("b"), "libs/b".into()); + input.toolchain_config = json!({ + "inferRelationships": true + }); + + let output = plugin.extend_project_graph(input).await; + + // Both projects derive their import path from the root go.mod, + // so only "a" has anything to output. + assert_eq!( + output.extended_projects, + BTreeMap::from_iter([( + Id::raw("a"), + ExtendProjectOutput { + dependencies: vec![ProjectDependency { + id: Id::raw("b"), + scope: DependencyScope::Production, + via: Some("package example.com/org/libs/b".into()), + }], + ..Default::default() + } + )]) + ); + + assert_eq!(output.input_files, [VirtualPath::new("/workspace/go.mod")]); + } + + #[tokio::test(flavor = "multi_thread")] + async fn doesnt_infer_edges_to_nested_projects_it_never_imports() { + let sandbox = create_moon_sandbox("projects-single-module"); + let plugin = sandbox.create_toolchain("go").await; + + let mut input = ExtendProjectGraphInput::default(); + input.project_sources.insert(Id::raw("a"), "apps/a".into()); + input.project_sources.insert(Id::raw("b"), "libs/b".into()); + input + .project_sources + .insert(Id::raw("tool"), "apps/a/tool".into()); + input.toolchain_config = json!({ + "inferRelationships": true + }); + + let output = plugin.extend_project_graph(input).await; + + // `go list -deps ./...` run from `a` enumerates the nested + // `tool` project's package as a root even though nothing + // imports it; ownership must not become a dependency edge. + assert_eq!( + output.extended_projects, + BTreeMap::from_iter([( + Id::raw("a"), + ExtendProjectOutput { + dependencies: vec![ProjectDependency { + id: Id::raw("b"), + scope: DependencyScope::Production, + via: Some("package example.com/org/libs/b".into()), + }], + ..Default::default() + } + )]) + ); + } + #[tokio::test(flavor = "multi_thread")] async fn doesnt_infer_relations_if_config_disabled() { let sandbox = create_moon_sandbox("projects-workspace"); @@ -381,7 +480,7 @@ mod go_toolchain_tier2 { dependencies: vec![ProjectDependency { id: Id::raw("a"), scope: DependencyScope::Production, - via: Some("module example.com/org/a".into()), + via: Some("package example.com/org/a".into()), }], ..Default::default() }) @@ -402,10 +501,9 @@ mod go_toolchain_tier2 { let output = plugin.extend_project_graph(input).await; - // `e` imports `example.com/org/a` only through the `a/pkg` - // subpackage, so the dependency is only inferred when - // `go list -deps` emits the owning module path via - // `-f {{if .Module}}{{.Module.Path}}{{end}}`. + // `e` never imports `example.com/org/a` itself, only the + // `a/pkg` subpackage, so the dependency relies on prefix + // matching rather than an exact import path match. assert_eq!( output.extended_projects.get(&Id::raw("e")), Some(&ExtendProjectOutput { @@ -413,7 +511,7 @@ mod go_toolchain_tier2 { dependencies: vec![ProjectDependency { id: Id::raw("a"), scope: DependencyScope::Production, - via: Some("module example.com/org/a".into()), + via: Some("package example.com/org/a/pkg".into()), }], ..Default::default() }) From c949f4e79b46353fe34ee97aa0133d48af495aab Mon Sep 17 00:00:00 2001 From: Miles Johnson Date: Fri, 14 Aug 2026 08:51:25 -0700 Subject: [PATCH 75/78] chore: Release --- Cargo.lock | 2 +- toolchains/go/CHANGELOG.md | 2 +- toolchains/go/Cargo.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4e8b8f3f..8bb40ba8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1984,7 +1984,7 @@ dependencies = [ [[package]] name = "go_toolchain" -version = "1.4.7" +version = "1.5.0" dependencies = [ "extism-pdk", "go_tool", diff --git a/toolchains/go/CHANGELOG.md b/toolchains/go/CHANGELOG.md index dcaa9049..1e0543be 100644 --- a/toolchains/go/CHANGELOG.md +++ b/toolchains/go/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## Unreleased +## 1.5.0 #### 🚀 Updates diff --git a/toolchains/go/Cargo.toml b/toolchains/go/Cargo.toml index 6c629d43..ba52fe00 100644 --- a/toolchains/go/Cargo.toml +++ b/toolchains/go/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "go_toolchain" -version = "1.4.7" +version = "1.5.0" edition = "2024" description = "Go toolchain WASM plugin for moon." authors = ["Miles Johnson"] From 9152366a52a681c011b8301319d4ab1bba60886a Mon Sep 17 00:00:00 2001 From: milesj Date: Sat, 15 Aug 2026 04:44:44 +0000 Subject: [PATCH 76/78] chore: Update tool releases [skip ci] --- tools/python/releases-v2.json | 379 ++++++++++++++++++++++++++-------- tools/python/releases.json | 338 ++++++++++++++++++++++-------- 2 files changed, 549 insertions(+), 168 deletions(-) diff --git a/tools/python/releases-v2.json b/tools/python/releases-v2.json index 1ec8e782..2302000c 100644 --- a/tools/python/releases-v2.json +++ b/tools/python/releases-v2.json @@ -665,6 +665,73 @@ "sha": 1 } }, + "3.10.21": { + "aarch64-apple-darwin": { + "file": "cpython-3.10.21+20260814-aarch64-apple-darwin-install_only.tar.gz", + "release": "20260814", + "sha": 1 + }, + "aarch64-unknown-linux-gnu": { + "file": "cpython-3.10.21+20260814-aarch64-unknown-linux-gnu-install_only.tar.gz", + "release": "20260814", + "sha": 1 + }, + "aarch64-unknown-linux-musl": { + "file": "cpython-3.10.21+20260814-aarch64-unknown-linux-musl-install_only.tar.gz", + "release": "20260814", + "sha": 1 + }, + "armv7-unknown-linux-gnueabi": { + "file": "cpython-3.10.21+20260814-armv7-unknown-linux-gnueabi-install_only.tar.gz", + "release": "20260814", + "sha": 1 + }, + "armv7-unknown-linux-gnueabihf": { + "file": "cpython-3.10.21+20260814-armv7-unknown-linux-gnueabihf-install_only.tar.gz", + "release": "20260814", + "sha": 1 + }, + "i686-pc-windows-msvc": { + "file": "cpython-3.10.21+20260814-i686-pc-windows-msvc-install_only.tar.gz", + "release": "20260814", + "sha": 1 + }, + "powerpc64le-unknown-linux-gnu": { + "file": "cpython-3.10.21+20260814-ppc64le-unknown-linux-gnu-install_only.tar.gz", + "release": "20260814", + "sha": 1 + }, + "riscv64gc-unknown-linux-gnu": { + "file": "cpython-3.10.21+20260814-riscv64-unknown-linux-gnu-install_only.tar.gz", + "release": "20260814", + "sha": 1 + }, + "s390x-unknown-linux-gnu": { + "file": "cpython-3.10.21+20260814-s390x-unknown-linux-gnu-install_only.tar.gz", + "release": "20260814", + "sha": 1 + }, + "x86_64-apple-darwin": { + "file": "cpython-3.10.21+20260814-x86_64-apple-darwin-install_only.tar.gz", + "release": "20260814", + "sha": 1 + }, + "x86_64-pc-windows-msvc": { + "file": "cpython-3.10.21+20260814-x86_64-pc-windows-msvc-install_only.tar.gz", + "release": "20260814", + "sha": 1 + }, + "x86_64-unknown-linux-gnu": { + "file": "cpython-3.10.21+20260814-x86_64-unknown-linux-gnu-install_only.tar.gz", + "release": "20260814", + "sha": 1 + }, + "x86_64-unknown-linux-musl": { + "file": "cpython-3.10.21+20260814-x86_64-unknown-linux-musl-install_only.tar.gz", + "release": "20260814", + "sha": 1 + } + }, "3.10.3": { "aarch64-apple-darwin": { "file": "cpython-3.10.3+20220318-aarch64-apple-darwin-install_only.tar.gz", @@ -1398,6 +1465,78 @@ "sha": 1 } }, + "3.11.16": { + "aarch64-apple-darwin": { + "file": "cpython-3.11.16+20260814-aarch64-apple-darwin-install_only.tar.gz", + "release": "20260814", + "sha": 1 + }, + "aarch64-pc-windows-msvc": { + "file": "cpython-3.11.16+20260814-aarch64-pc-windows-msvc-install_only.tar.gz", + "release": "20260814", + "sha": 1 + }, + "aarch64-unknown-linux-gnu": { + "file": "cpython-3.11.16+20260814-aarch64-unknown-linux-gnu-install_only.tar.gz", + "release": "20260814", + "sha": 1 + }, + "aarch64-unknown-linux-musl": { + "file": "cpython-3.11.16+20260814-aarch64-unknown-linux-musl-install_only.tar.gz", + "release": "20260814", + "sha": 1 + }, + "armv7-unknown-linux-gnueabi": { + "file": "cpython-3.11.16+20260814-armv7-unknown-linux-gnueabi-install_only.tar.gz", + "release": "20260814", + "sha": 1 + }, + "armv7-unknown-linux-gnueabihf": { + "file": "cpython-3.11.16+20260814-armv7-unknown-linux-gnueabihf-install_only.tar.gz", + "release": "20260814", + "sha": 1 + }, + "i686-pc-windows-msvc": { + "file": "cpython-3.11.16+20260814-i686-pc-windows-msvc-install_only.tar.gz", + "release": "20260814", + "sha": 1 + }, + "powerpc64le-unknown-linux-gnu": { + "file": "cpython-3.11.16+20260814-ppc64le-unknown-linux-gnu-install_only.tar.gz", + "release": "20260814", + "sha": 1 + }, + "riscv64gc-unknown-linux-gnu": { + "file": "cpython-3.11.16+20260814-riscv64-unknown-linux-gnu-install_only.tar.gz", + "release": "20260814", + "sha": 1 + }, + "s390x-unknown-linux-gnu": { + "file": "cpython-3.11.16+20260814-s390x-unknown-linux-gnu-install_only.tar.gz", + "release": "20260814", + "sha": 1 + }, + "x86_64-apple-darwin": { + "file": "cpython-3.11.16+20260814-x86_64-apple-darwin-install_only.tar.gz", + "release": "20260814", + "sha": 1 + }, + "x86_64-pc-windows-msvc": { + "file": "cpython-3.11.16+20260814-x86_64-pc-windows-msvc-install_only.tar.gz", + "release": "20260814", + "sha": 1 + }, + "x86_64-unknown-linux-gnu": { + "file": "cpython-3.11.16+20260814-x86_64-unknown-linux-gnu-install_only.tar.gz", + "release": "20260814", + "sha": 1 + }, + "x86_64-unknown-linux-musl": { + "file": "cpython-3.11.16+20260814-x86_64-unknown-linux-musl-install_only.tar.gz", + "release": "20260814", + "sha": 1 + } + }, "3.11.3": { "aarch64-apple-darwin": { "file": "cpython-3.11.3+20230507-aarch64-apple-darwin-install_only.tar.gz", @@ -2119,6 +2258,78 @@ "sha": 1 } }, + "3.12.14": { + "aarch64-apple-darwin": { + "file": "cpython-3.12.14+20260814-aarch64-apple-darwin-install_only.tar.gz", + "release": "20260814", + "sha": 1 + }, + "aarch64-pc-windows-msvc": { + "file": "cpython-3.12.14+20260814-aarch64-pc-windows-msvc-install_only.tar.gz", + "release": "20260814", + "sha": 1 + }, + "aarch64-unknown-linux-gnu": { + "file": "cpython-3.12.14+20260814-aarch64-unknown-linux-gnu-install_only.tar.gz", + "release": "20260814", + "sha": 1 + }, + "aarch64-unknown-linux-musl": { + "file": "cpython-3.12.14+20260814-aarch64-unknown-linux-musl-install_only.tar.gz", + "release": "20260814", + "sha": 1 + }, + "armv7-unknown-linux-gnueabi": { + "file": "cpython-3.12.14+20260814-armv7-unknown-linux-gnueabi-install_only.tar.gz", + "release": "20260814", + "sha": 1 + }, + "armv7-unknown-linux-gnueabihf": { + "file": "cpython-3.12.14+20260814-armv7-unknown-linux-gnueabihf-install_only.tar.gz", + "release": "20260814", + "sha": 1 + }, + "i686-pc-windows-msvc": { + "file": "cpython-3.12.14+20260814-i686-pc-windows-msvc-install_only.tar.gz", + "release": "20260814", + "sha": 1 + }, + "powerpc64le-unknown-linux-gnu": { + "file": "cpython-3.12.14+20260814-ppc64le-unknown-linux-gnu-install_only.tar.gz", + "release": "20260814", + "sha": 1 + }, + "riscv64gc-unknown-linux-gnu": { + "file": "cpython-3.12.14+20260814-riscv64-unknown-linux-gnu-install_only.tar.gz", + "release": "20260814", + "sha": 1 + }, + "s390x-unknown-linux-gnu": { + "file": "cpython-3.12.14+20260814-s390x-unknown-linux-gnu-install_only.tar.gz", + "release": "20260814", + "sha": 1 + }, + "x86_64-apple-darwin": { + "file": "cpython-3.12.14+20260814-x86_64-apple-darwin-install_only.tar.gz", + "release": "20260814", + "sha": 1 + }, + "x86_64-pc-windows-msvc": { + "file": "cpython-3.12.14+20260814-x86_64-pc-windows-msvc-install_only.tar.gz", + "release": "20260814", + "sha": 1 + }, + "x86_64-unknown-linux-gnu": { + "file": "cpython-3.12.14+20260814-x86_64-unknown-linux-gnu-install_only.tar.gz", + "release": "20260814", + "sha": 1 + }, + "x86_64-unknown-linux-musl": { + "file": "cpython-3.12.14+20260814-x86_64-unknown-linux-musl-install_only.tar.gz", + "release": "20260814", + "sha": 1 + } + }, "3.12.2": { "aarch64-apple-darwin": { "file": "cpython-3.12.2+20240224-aarch64-apple-darwin-install_only.tar.gz", @@ -3170,73 +3381,73 @@ }, "3.13.15": { "aarch64-apple-darwin": { - "file": "cpython-3.13.15+20260807-aarch64-apple-darwin-install_only.tar.gz", - "release": "20260807", + "file": "cpython-3.13.15+20260814-aarch64-apple-darwin-install_only.tar.gz", + "release": "20260814", "sha": 1 }, "aarch64-pc-windows-msvc": { - "file": "cpython-3.13.15+20260807-aarch64-pc-windows-msvc-install_only.tar.gz", - "release": "20260807", + "file": "cpython-3.13.15+20260814-aarch64-pc-windows-msvc-install_only.tar.gz", + "release": "20260814", "sha": 1 }, "aarch64-unknown-linux-gnu": { - "file": "cpython-3.13.15+20260807-aarch64-unknown-linux-gnu-install_only.tar.gz", - "release": "20260807", + "file": "cpython-3.13.15+20260814-aarch64-unknown-linux-gnu-install_only.tar.gz", + "release": "20260814", "sha": 1 }, "aarch64-unknown-linux-musl": { - "file": "cpython-3.13.15+20260807-aarch64-unknown-linux-musl-install_only.tar.gz", - "release": "20260807", + "file": "cpython-3.13.15+20260814-aarch64-unknown-linux-musl-install_only.tar.gz", + "release": "20260814", "sha": 1 }, "armv7-unknown-linux-gnueabi": { - "file": "cpython-3.13.15+20260807-armv7-unknown-linux-gnueabi-install_only.tar.gz", - "release": "20260807", + "file": "cpython-3.13.15+20260814-armv7-unknown-linux-gnueabi-install_only.tar.gz", + "release": "20260814", "sha": 1 }, "armv7-unknown-linux-gnueabihf": { - "file": "cpython-3.13.15+20260807-armv7-unknown-linux-gnueabihf-install_only.tar.gz", - "release": "20260807", + "file": "cpython-3.13.15+20260814-armv7-unknown-linux-gnueabihf-install_only.tar.gz", + "release": "20260814", "sha": 1 }, "i686-pc-windows-msvc": { - "file": "cpython-3.13.15+20260807-i686-pc-windows-msvc-install_only.tar.gz", - "release": "20260807", + "file": "cpython-3.13.15+20260814-i686-pc-windows-msvc-install_only.tar.gz", + "release": "20260814", "sha": 1 }, "powerpc64le-unknown-linux-gnu": { - "file": "cpython-3.13.15+20260807-ppc64le-unknown-linux-gnu-install_only.tar.gz", - "release": "20260807", + "file": "cpython-3.13.15+20260814-ppc64le-unknown-linux-gnu-install_only.tar.gz", + "release": "20260814", "sha": 1 }, "riscv64gc-unknown-linux-gnu": { - "file": "cpython-3.13.15+20260807-riscv64-unknown-linux-gnu-install_only.tar.gz", - "release": "20260807", + "file": "cpython-3.13.15+20260814-riscv64-unknown-linux-gnu-install_only.tar.gz", + "release": "20260814", "sha": 1 }, "s390x-unknown-linux-gnu": { - "file": "cpython-3.13.15+20260807-s390x-unknown-linux-gnu-install_only.tar.gz", - "release": "20260807", + "file": "cpython-3.13.15+20260814-s390x-unknown-linux-gnu-install_only.tar.gz", + "release": "20260814", "sha": 1 }, "x86_64-apple-darwin": { - "file": "cpython-3.13.15+20260807-x86_64-apple-darwin-install_only.tar.gz", - "release": "20260807", + "file": "cpython-3.13.15+20260814-x86_64-apple-darwin-install_only.tar.gz", + "release": "20260814", "sha": 1 }, "x86_64-pc-windows-msvc": { - "file": "cpython-3.13.15+20260807-x86_64-pc-windows-msvc-install_only.tar.gz", - "release": "20260807", + "file": "cpython-3.13.15+20260814-x86_64-pc-windows-msvc-install_only.tar.gz", + "release": "20260814", "sha": 1 }, "x86_64-unknown-linux-gnu": { - "file": "cpython-3.13.15+20260807-x86_64-unknown-linux-gnu-install_only.tar.gz", - "release": "20260807", + "file": "cpython-3.13.15+20260814-x86_64-unknown-linux-gnu-install_only.tar.gz", + "release": "20260814", "sha": 1 }, "x86_64-unknown-linux-musl": { - "file": "cpython-3.13.15+20260807-x86_64-unknown-linux-musl-install_only.tar.gz", - "release": "20260807", + "file": "cpython-3.13.15+20260814-x86_64-unknown-linux-musl-install_only.tar.gz", + "release": "20260814", "sha": 1 } }, @@ -5098,73 +5309,73 @@ }, "3.14.7": { "aarch64-apple-darwin": { - "file": "cpython-3.14.7+20260807-aarch64-apple-darwin-install_only.tar.gz", - "release": "20260807", + "file": "cpython-3.14.7+20260814-aarch64-apple-darwin-install_only.tar.gz", + "release": "20260814", "sha": 1 }, "aarch64-pc-windows-msvc": { - "file": "cpython-3.14.7+20260807-aarch64-pc-windows-msvc-install_only.tar.gz", - "release": "20260807", + "file": "cpython-3.14.7+20260814-aarch64-pc-windows-msvc-install_only.tar.gz", + "release": "20260814", "sha": 1 }, "aarch64-unknown-linux-gnu": { - "file": "cpython-3.14.7+20260807-aarch64-unknown-linux-gnu-install_only.tar.gz", - "release": "20260807", + "file": "cpython-3.14.7+20260814-aarch64-unknown-linux-gnu-install_only.tar.gz", + "release": "20260814", "sha": 1 }, "aarch64-unknown-linux-musl": { - "file": "cpython-3.14.7+20260807-aarch64-unknown-linux-musl-install_only.tar.gz", - "release": "20260807", + "file": "cpython-3.14.7+20260814-aarch64-unknown-linux-musl-install_only.tar.gz", + "release": "20260814", "sha": 1 }, "armv7-unknown-linux-gnueabi": { - "file": "cpython-3.14.7+20260807-armv7-unknown-linux-gnueabi-install_only.tar.gz", - "release": "20260807", + "file": "cpython-3.14.7+20260814-armv7-unknown-linux-gnueabi-install_only.tar.gz", + "release": "20260814", "sha": 1 }, "armv7-unknown-linux-gnueabihf": { - "file": "cpython-3.14.7+20260807-armv7-unknown-linux-gnueabihf-install_only.tar.gz", - "release": "20260807", + "file": "cpython-3.14.7+20260814-armv7-unknown-linux-gnueabihf-install_only.tar.gz", + "release": "20260814", "sha": 1 }, "i686-pc-windows-msvc": { - "file": "cpython-3.14.7+20260807-i686-pc-windows-msvc-install_only.tar.gz", - "release": "20260807", + "file": "cpython-3.14.7+20260814-i686-pc-windows-msvc-install_only.tar.gz", + "release": "20260814", "sha": 1 }, "powerpc64le-unknown-linux-gnu": { - "file": "cpython-3.14.7+20260807-ppc64le-unknown-linux-gnu-install_only.tar.gz", - "release": "20260807", + "file": "cpython-3.14.7+20260814-ppc64le-unknown-linux-gnu-install_only.tar.gz", + "release": "20260814", "sha": 1 }, "riscv64gc-unknown-linux-gnu": { - "file": "cpython-3.14.7+20260807-riscv64-unknown-linux-gnu-install_only.tar.gz", - "release": "20260807", + "file": "cpython-3.14.7+20260814-riscv64-unknown-linux-gnu-install_only.tar.gz", + "release": "20260814", "sha": 1 }, "s390x-unknown-linux-gnu": { - "file": "cpython-3.14.7+20260807-s390x-unknown-linux-gnu-install_only.tar.gz", - "release": "20260807", + "file": "cpython-3.14.7+20260814-s390x-unknown-linux-gnu-install_only.tar.gz", + "release": "20260814", "sha": 1 }, "x86_64-apple-darwin": { - "file": "cpython-3.14.7+20260807-x86_64-apple-darwin-install_only.tar.gz", - "release": "20260807", + "file": "cpython-3.14.7+20260814-x86_64-apple-darwin-install_only.tar.gz", + "release": "20260814", "sha": 1 }, "x86_64-pc-windows-msvc": { - "file": "cpython-3.14.7+20260807-x86_64-pc-windows-msvc-install_only.tar.gz", - "release": "20260807", + "file": "cpython-3.14.7+20260814-x86_64-pc-windows-msvc-install_only.tar.gz", + "release": "20260814", "sha": 1 }, "x86_64-unknown-linux-gnu": { - "file": "cpython-3.14.7+20260807-x86_64-unknown-linux-gnu-install_only.tar.gz", - "release": "20260807", + "file": "cpython-3.14.7+20260814-x86_64-unknown-linux-gnu-install_only.tar.gz", + "release": "20260814", "sha": 1 }, "x86_64-unknown-linux-musl": { - "file": "cpython-3.14.7+20260807-x86_64-unknown-linux-musl-install_only.tar.gz", - "release": "20260807", + "file": "cpython-3.14.7+20260814-x86_64-unknown-linux-musl-install_only.tar.gz", + "release": "20260814", "sha": 1 } }, @@ -6034,73 +6245,73 @@ }, "3.15.0-rc.1": { "aarch64-apple-darwin": { - "file": "cpython-3.15.0rc1+20260807-aarch64-apple-darwin-install_only.tar.gz", - "release": "20260807", + "file": "cpython-3.15.0rc1+20260814-aarch64-apple-darwin-install_only.tar.gz", + "release": "20260814", "sha": 1 }, "aarch64-pc-windows-msvc": { - "file": "cpython-3.15.0rc1+20260807-aarch64-pc-windows-msvc-install_only.tar.gz", - "release": "20260807", + "file": "cpython-3.15.0rc1+20260814-aarch64-pc-windows-msvc-install_only.tar.gz", + "release": "20260814", "sha": 1 }, "aarch64-unknown-linux-gnu": { - "file": "cpython-3.15.0rc1+20260807-aarch64-unknown-linux-gnu-install_only.tar.gz", - "release": "20260807", + "file": "cpython-3.15.0rc1+20260814-aarch64-unknown-linux-gnu-install_only.tar.gz", + "release": "20260814", "sha": 1 }, "aarch64-unknown-linux-musl": { - "file": "cpython-3.15.0rc1+20260807-aarch64-unknown-linux-musl-install_only.tar.gz", - "release": "20260807", + "file": "cpython-3.15.0rc1+20260814-aarch64-unknown-linux-musl-install_only.tar.gz", + "release": "20260814", "sha": 1 }, "armv7-unknown-linux-gnueabi": { - "file": "cpython-3.15.0rc1+20260807-armv7-unknown-linux-gnueabi-install_only.tar.gz", - "release": "20260807", + "file": "cpython-3.15.0rc1+20260814-armv7-unknown-linux-gnueabi-install_only.tar.gz", + "release": "20260814", "sha": 1 }, "armv7-unknown-linux-gnueabihf": { - "file": "cpython-3.15.0rc1+20260807-armv7-unknown-linux-gnueabihf-install_only.tar.gz", - "release": "20260807", + "file": "cpython-3.15.0rc1+20260814-armv7-unknown-linux-gnueabihf-install_only.tar.gz", + "release": "20260814", "sha": 1 }, "i686-pc-windows-msvc": { - "file": "cpython-3.15.0rc1+20260807-i686-pc-windows-msvc-install_only.tar.gz", - "release": "20260807", + "file": "cpython-3.15.0rc1+20260814-i686-pc-windows-msvc-install_only.tar.gz", + "release": "20260814", "sha": 1 }, "powerpc64le-unknown-linux-gnu": { - "file": "cpython-3.15.0rc1+20260807-ppc64le-unknown-linux-gnu-install_only.tar.gz", - "release": "20260807", + "file": "cpython-3.15.0rc1+20260814-ppc64le-unknown-linux-gnu-install_only.tar.gz", + "release": "20260814", "sha": 1 }, "riscv64gc-unknown-linux-gnu": { - "file": "cpython-3.15.0rc1+20260807-riscv64-unknown-linux-gnu-install_only.tar.gz", - "release": "20260807", + "file": "cpython-3.15.0rc1+20260814-riscv64-unknown-linux-gnu-install_only.tar.gz", + "release": "20260814", "sha": 1 }, "s390x-unknown-linux-gnu": { - "file": "cpython-3.15.0rc1+20260807-s390x-unknown-linux-gnu-install_only.tar.gz", - "release": "20260807", + "file": "cpython-3.15.0rc1+20260814-s390x-unknown-linux-gnu-install_only.tar.gz", + "release": "20260814", "sha": 1 }, "x86_64-apple-darwin": { - "file": "cpython-3.15.0rc1+20260807-x86_64-apple-darwin-install_only.tar.gz", - "release": "20260807", + "file": "cpython-3.15.0rc1+20260814-x86_64-apple-darwin-install_only.tar.gz", + "release": "20260814", "sha": 1 }, "x86_64-pc-windows-msvc": { - "file": "cpython-3.15.0rc1+20260807-x86_64-pc-windows-msvc-install_only.tar.gz", - "release": "20260807", + "file": "cpython-3.15.0rc1+20260814-x86_64-pc-windows-msvc-install_only.tar.gz", + "release": "20260814", "sha": 1 }, "x86_64-unknown-linux-gnu": { - "file": "cpython-3.15.0rc1+20260807-x86_64-unknown-linux-gnu-install_only.tar.gz", - "release": "20260807", + "file": "cpython-3.15.0rc1+20260814-x86_64-unknown-linux-gnu-install_only.tar.gz", + "release": "20260814", "sha": 1 }, "x86_64-unknown-linux-musl": { - "file": "cpython-3.15.0rc1+20260807-x86_64-unknown-linux-musl-install_only.tar.gz", - "release": "20260807", + "file": "cpython-3.15.0rc1+20260814-x86_64-unknown-linux-musl-install_only.tar.gz", + "release": "20260814", "sha": 1 } }, diff --git a/tools/python/releases.json b/tools/python/releases.json index 1364b6ea..993e5b5c 100644 --- a/tools/python/releases.json +++ b/tools/python/releases.json @@ -535,6 +535,60 @@ "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/cpython-3.10.20+20260807-x86_64-unknown-linux-musl-lto-full.tar.zst" } }, + "3.10.21": { + "aarch64-apple-darwin": { + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260814/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260814/cpython-3.10.21+20260814-aarch64-apple-darwin-pgo+lto-full.tar.zst" + }, + "aarch64-unknown-linux-gnu": { + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260814/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260814/cpython-3.10.21+20260814-aarch64-unknown-linux-gnu-pgo+lto-full.tar.zst" + }, + "aarch64-unknown-linux-musl": { + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260814/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260814/cpython-3.10.21+20260814-aarch64-unknown-linux-musl-lto-full.tar.zst" + }, + "armv7-unknown-linux-gnueabi": { + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260814/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260814/cpython-3.10.21+20260814-armv7-unknown-linux-gnueabi-lto-full.tar.zst" + }, + "armv7-unknown-linux-gnueabihf": { + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260814/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260814/cpython-3.10.21+20260814-armv7-unknown-linux-gnueabihf-lto-full.tar.zst" + }, + "i686-pc-windows-msvc": { + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260814/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260814/cpython-3.10.21+20260814-i686-pc-windows-msvc-pgo-full.tar.zst" + }, + "powerpc64le-unknown-linux-gnu": { + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260814/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260814/cpython-3.10.21+20260814-ppc64le-unknown-linux-gnu-lto-full.tar.zst" + }, + "riscv64gc-unknown-linux-gnu": { + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260814/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260814/cpython-3.10.21+20260814-riscv64-unknown-linux-gnu-lto-full.tar.zst" + }, + "s390x-unknown-linux-gnu": { + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260814/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260814/cpython-3.10.21+20260814-s390x-unknown-linux-gnu-lto-full.tar.zst" + }, + "x86_64-apple-darwin": { + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260814/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260814/cpython-3.10.21+20260814-x86_64-apple-darwin-pgo+lto-full.tar.zst" + }, + "x86_64-pc-windows-msvc": { + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260814/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260814/cpython-3.10.21+20260814-x86_64-pc-windows-msvc-pgo-full.tar.zst" + }, + "x86_64-unknown-linux-gnu": { + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260814/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260814/cpython-3.10.21+20260814-x86_64-unknown-linux-gnu-pgo+lto-full.tar.zst" + }, + "x86_64-unknown-linux-musl": { + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260814/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260814/cpython-3.10.21+20260814-x86_64-unknown-linux-musl-lto-full.tar.zst" + } + }, "3.10.3": { "aarch64-apple-darwin": { "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20220318/cpython-3.10.3+20220318-aarch64-apple-darwin-pgo+lto-full.tar.zst.sha256", @@ -1127,6 +1181,64 @@ "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/cpython-3.11.15+20260807-x86_64-unknown-linux-musl-lto-full.tar.zst" } }, + "3.11.16": { + "aarch64-apple-darwin": { + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260814/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260814/cpython-3.11.16+20260814-aarch64-apple-darwin-pgo+lto-full.tar.zst" + }, + "aarch64-pc-windows-msvc": { + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260814/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260814/cpython-3.11.16+20260814-aarch64-pc-windows-msvc-pgo-full.tar.zst" + }, + "aarch64-unknown-linux-gnu": { + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260814/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260814/cpython-3.11.16+20260814-aarch64-unknown-linux-gnu-pgo+lto-full.tar.zst" + }, + "aarch64-unknown-linux-musl": { + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260814/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260814/cpython-3.11.16+20260814-aarch64-unknown-linux-musl-lto-full.tar.zst" + }, + "armv7-unknown-linux-gnueabi": { + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260814/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260814/cpython-3.11.16+20260814-armv7-unknown-linux-gnueabi-lto-full.tar.zst" + }, + "armv7-unknown-linux-gnueabihf": { + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260814/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260814/cpython-3.11.16+20260814-armv7-unknown-linux-gnueabihf-lto-full.tar.zst" + }, + "i686-pc-windows-msvc": { + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260814/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260814/cpython-3.11.16+20260814-i686-pc-windows-msvc-pgo-full.tar.zst" + }, + "powerpc64le-unknown-linux-gnu": { + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260814/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260814/cpython-3.11.16+20260814-ppc64le-unknown-linux-gnu-lto-full.tar.zst" + }, + "riscv64gc-unknown-linux-gnu": { + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260814/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260814/cpython-3.11.16+20260814-riscv64-unknown-linux-gnu-lto-full.tar.zst" + }, + "s390x-unknown-linux-gnu": { + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260814/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260814/cpython-3.11.16+20260814-s390x-unknown-linux-gnu-lto-full.tar.zst" + }, + "x86_64-apple-darwin": { + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260814/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260814/cpython-3.11.16+20260814-x86_64-apple-darwin-pgo+lto-full.tar.zst" + }, + "x86_64-pc-windows-msvc": { + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260814/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260814/cpython-3.11.16+20260814-x86_64-pc-windows-msvc-pgo-full.tar.zst" + }, + "x86_64-unknown-linux-gnu": { + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260814/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260814/cpython-3.11.16+20260814-x86_64-unknown-linux-gnu-pgo+lto-full.tar.zst" + }, + "x86_64-unknown-linux-musl": { + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260814/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260814/cpython-3.11.16+20260814-x86_64-unknown-linux-musl-lto-full.tar.zst" + } + }, "3.11.3": { "aarch64-apple-darwin": { "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20230507/cpython-3.11.3+20230507-aarch64-apple-darwin-pgo+lto-full.tar.zst.sha256", @@ -1709,6 +1821,64 @@ "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/cpython-3.12.13+20260807-x86_64-unknown-linux-musl-lto-full.tar.zst" } }, + "3.12.14": { + "aarch64-apple-darwin": { + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260814/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260814/cpython-3.12.14+20260814-aarch64-apple-darwin-pgo+lto-full.tar.zst" + }, + "aarch64-pc-windows-msvc": { + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260814/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260814/cpython-3.12.14+20260814-aarch64-pc-windows-msvc-pgo-full.tar.zst" + }, + "aarch64-unknown-linux-gnu": { + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260814/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260814/cpython-3.12.14+20260814-aarch64-unknown-linux-gnu-pgo+lto-full.tar.zst" + }, + "aarch64-unknown-linux-musl": { + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260814/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260814/cpython-3.12.14+20260814-aarch64-unknown-linux-musl-lto-full.tar.zst" + }, + "armv7-unknown-linux-gnueabi": { + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260814/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260814/cpython-3.12.14+20260814-armv7-unknown-linux-gnueabi-lto-full.tar.zst" + }, + "armv7-unknown-linux-gnueabihf": { + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260814/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260814/cpython-3.12.14+20260814-armv7-unknown-linux-gnueabihf-lto-full.tar.zst" + }, + "i686-pc-windows-msvc": { + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260814/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260814/cpython-3.12.14+20260814-i686-pc-windows-msvc-pgo-full.tar.zst" + }, + "powerpc64le-unknown-linux-gnu": { + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260814/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260814/cpython-3.12.14+20260814-ppc64le-unknown-linux-gnu-lto-full.tar.zst" + }, + "riscv64gc-unknown-linux-gnu": { + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260814/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260814/cpython-3.12.14+20260814-riscv64-unknown-linux-gnu-lto-full.tar.zst" + }, + "s390x-unknown-linux-gnu": { + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260814/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260814/cpython-3.12.14+20260814-s390x-unknown-linux-gnu-lto-full.tar.zst" + }, + "x86_64-apple-darwin": { + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260814/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260814/cpython-3.12.14+20260814-x86_64-apple-darwin-pgo+lto-full.tar.zst" + }, + "x86_64-pc-windows-msvc": { + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260814/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260814/cpython-3.12.14+20260814-x86_64-pc-windows-msvc-pgo-full.tar.zst" + }, + "x86_64-unknown-linux-gnu": { + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260814/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260814/cpython-3.12.14+20260814-x86_64-unknown-linux-gnu-pgo+lto-full.tar.zst" + }, + "x86_64-unknown-linux-musl": { + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260814/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260814/cpython-3.12.14+20260814-x86_64-unknown-linux-musl-lto-full.tar.zst" + } + }, "3.12.2": { "aarch64-apple-darwin": { "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20240224/cpython-3.12.2+20240224-aarch64-apple-darwin-pgo+lto-full.tar.zst.sha256", @@ -2557,60 +2727,60 @@ }, "3.13.15": { "aarch64-apple-darwin": { - "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/SHA256SUMS", - "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/cpython-3.13.15+20260807-aarch64-apple-darwin-pgo+lto-full.tar.zst" + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260814/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260814/cpython-3.13.15+20260814-aarch64-apple-darwin-pgo+lto-full.tar.zst" }, "aarch64-pc-windows-msvc": { - "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/SHA256SUMS", - "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/cpython-3.13.15+20260807-aarch64-pc-windows-msvc-pgo-full.tar.zst" + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260814/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260814/cpython-3.13.15+20260814-aarch64-pc-windows-msvc-pgo-full.tar.zst" }, "aarch64-unknown-linux-gnu": { - "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/SHA256SUMS", - "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/cpython-3.13.15+20260807-aarch64-unknown-linux-gnu-pgo+lto-full.tar.zst" + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260814/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260814/cpython-3.13.15+20260814-aarch64-unknown-linux-gnu-pgo+lto-full.tar.zst" }, "aarch64-unknown-linux-musl": { - "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/SHA256SUMS", - "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/cpython-3.13.15+20260807-aarch64-unknown-linux-musl-lto-full.tar.zst" + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260814/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260814/cpython-3.13.15+20260814-aarch64-unknown-linux-musl-lto-full.tar.zst" }, "armv7-unknown-linux-gnueabi": { - "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/SHA256SUMS", - "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/cpython-3.13.15+20260807-armv7-unknown-linux-gnueabi-lto-full.tar.zst" + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260814/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260814/cpython-3.13.15+20260814-armv7-unknown-linux-gnueabi-lto-full.tar.zst" }, "armv7-unknown-linux-gnueabihf": { - "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/SHA256SUMS", - "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/cpython-3.13.15+20260807-armv7-unknown-linux-gnueabihf-lto-full.tar.zst" + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260814/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260814/cpython-3.13.15+20260814-armv7-unknown-linux-gnueabihf-lto-full.tar.zst" }, "i686-pc-windows-msvc": { - "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/SHA256SUMS", - "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/cpython-3.13.15+20260807-i686-pc-windows-msvc-pgo-full.tar.zst" + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260814/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260814/cpython-3.13.15+20260814-i686-pc-windows-msvc-pgo-full.tar.zst" }, "powerpc64le-unknown-linux-gnu": { - "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/SHA256SUMS", - "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/cpython-3.13.15+20260807-ppc64le-unknown-linux-gnu-lto-full.tar.zst" + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260814/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260814/cpython-3.13.15+20260814-ppc64le-unknown-linux-gnu-lto-full.tar.zst" }, "riscv64gc-unknown-linux-gnu": { - "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/SHA256SUMS", - "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/cpython-3.13.15+20260807-riscv64-unknown-linux-gnu-lto-full.tar.zst" + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260814/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260814/cpython-3.13.15+20260814-riscv64-unknown-linux-gnu-lto-full.tar.zst" }, "s390x-unknown-linux-gnu": { - "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/SHA256SUMS", - "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/cpython-3.13.15+20260807-s390x-unknown-linux-gnu-lto-full.tar.zst" + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260814/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260814/cpython-3.13.15+20260814-s390x-unknown-linux-gnu-lto-full.tar.zst" }, "x86_64-apple-darwin": { - "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/SHA256SUMS", - "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/cpython-3.13.15+20260807-x86_64-apple-darwin-pgo+lto-full.tar.zst" + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260814/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260814/cpython-3.13.15+20260814-x86_64-apple-darwin-pgo+lto-full.tar.zst" }, "x86_64-pc-windows-msvc": { - "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/SHA256SUMS", - "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/cpython-3.13.15+20260807-x86_64-pc-windows-msvc-pgo-full.tar.zst" + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260814/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260814/cpython-3.13.15+20260814-x86_64-pc-windows-msvc-pgo-full.tar.zst" }, "x86_64-unknown-linux-gnu": { - "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/SHA256SUMS", - "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/cpython-3.13.15+20260807-x86_64-unknown-linux-gnu-pgo+lto-full.tar.zst" + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260814/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260814/cpython-3.13.15+20260814-x86_64-unknown-linux-gnu-pgo+lto-full.tar.zst" }, "x86_64-unknown-linux-musl": { - "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/SHA256SUMS", - "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/cpython-3.13.15+20260807-x86_64-unknown-linux-musl-lto-full.tar.zst" + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260814/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260814/cpython-3.13.15+20260814-x86_64-unknown-linux-musl-lto-full.tar.zst" } }, "3.13.2": { @@ -4111,60 +4281,60 @@ }, "3.14.7": { "aarch64-apple-darwin": { - "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/SHA256SUMS", - "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/cpython-3.14.7+20260807-aarch64-apple-darwin-pgo+lto-full.tar.zst" + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260814/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260814/cpython-3.14.7+20260814-aarch64-apple-darwin-pgo+lto-full.tar.zst" }, "aarch64-pc-windows-msvc": { - "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/SHA256SUMS", - "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/cpython-3.14.7+20260807-aarch64-pc-windows-msvc-pgo-full.tar.zst" + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260814/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260814/cpython-3.14.7+20260814-aarch64-pc-windows-msvc-pgo-full.tar.zst" }, "aarch64-unknown-linux-gnu": { - "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/SHA256SUMS", - "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/cpython-3.14.7+20260807-aarch64-unknown-linux-gnu-pgo+lto-full.tar.zst" + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260814/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260814/cpython-3.14.7+20260814-aarch64-unknown-linux-gnu-pgo+lto-full.tar.zst" }, "aarch64-unknown-linux-musl": { - "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/SHA256SUMS", - "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/cpython-3.14.7+20260807-aarch64-unknown-linux-musl-lto-full.tar.zst" + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260814/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260814/cpython-3.14.7+20260814-aarch64-unknown-linux-musl-lto-full.tar.zst" }, "armv7-unknown-linux-gnueabi": { - "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/SHA256SUMS", - "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/cpython-3.14.7+20260807-armv7-unknown-linux-gnueabi-lto-full.tar.zst" + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260814/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260814/cpython-3.14.7+20260814-armv7-unknown-linux-gnueabi-lto-full.tar.zst" }, "armv7-unknown-linux-gnueabihf": { - "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/SHA256SUMS", - "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/cpython-3.14.7+20260807-armv7-unknown-linux-gnueabihf-lto-full.tar.zst" + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260814/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260814/cpython-3.14.7+20260814-armv7-unknown-linux-gnueabihf-lto-full.tar.zst" }, "i686-pc-windows-msvc": { - "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/SHA256SUMS", - "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/cpython-3.14.7+20260807-i686-pc-windows-msvc-pgo-full.tar.zst" + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260814/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260814/cpython-3.14.7+20260814-i686-pc-windows-msvc-pgo-full.tar.zst" }, "powerpc64le-unknown-linux-gnu": { - "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/SHA256SUMS", - "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/cpython-3.14.7+20260807-ppc64le-unknown-linux-gnu-lto-full.tar.zst" + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260814/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260814/cpython-3.14.7+20260814-ppc64le-unknown-linux-gnu-lto-full.tar.zst" }, "riscv64gc-unknown-linux-gnu": { - "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/SHA256SUMS", - "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/cpython-3.14.7+20260807-riscv64-unknown-linux-gnu-lto-full.tar.zst" + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260814/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260814/cpython-3.14.7+20260814-riscv64-unknown-linux-gnu-lto-full.tar.zst" }, "s390x-unknown-linux-gnu": { - "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/SHA256SUMS", - "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/cpython-3.14.7+20260807-s390x-unknown-linux-gnu-lto-full.tar.zst" + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260814/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260814/cpython-3.14.7+20260814-s390x-unknown-linux-gnu-lto-full.tar.zst" }, "x86_64-apple-darwin": { - "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/SHA256SUMS", - "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/cpython-3.14.7+20260807-x86_64-apple-darwin-pgo+lto-full.tar.zst" + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260814/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260814/cpython-3.14.7+20260814-x86_64-apple-darwin-pgo+lto-full.tar.zst" }, "x86_64-pc-windows-msvc": { - "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/SHA256SUMS", - "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/cpython-3.14.7+20260807-x86_64-pc-windows-msvc-pgo-full.tar.zst" + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260814/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260814/cpython-3.14.7+20260814-x86_64-pc-windows-msvc-pgo-full.tar.zst" }, "x86_64-unknown-linux-gnu": { - "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/SHA256SUMS", - "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/cpython-3.14.7+20260807-x86_64-unknown-linux-gnu-pgo+lto-full.tar.zst" + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260814/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260814/cpython-3.14.7+20260814-x86_64-unknown-linux-gnu-pgo+lto-full.tar.zst" }, "x86_64-unknown-linux-musl": { - "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/SHA256SUMS", - "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/cpython-3.14.7+20260807-x86_64-unknown-linux-musl-lto-full.tar.zst" + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260814/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260814/cpython-3.14.7+20260814-x86_64-unknown-linux-musl-lto-full.tar.zst" } }, "3.15.0-a.1": { @@ -4865,60 +5035,60 @@ }, "3.15.0-rc.1": { "aarch64-apple-darwin": { - "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/SHA256SUMS", - "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/cpython-3.15.0rc1+20260807-aarch64-apple-darwin-pgo+lto-full.tar.zst" + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260814/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260814/cpython-3.15.0rc1+20260814-aarch64-apple-darwin-pgo+lto-full.tar.zst" }, "aarch64-pc-windows-msvc": { - "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/SHA256SUMS", - "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/cpython-3.15.0rc1+20260807-aarch64-pc-windows-msvc-pgo-full.tar.zst" + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260814/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260814/cpython-3.15.0rc1+20260814-aarch64-pc-windows-msvc-pgo-full.tar.zst" }, "aarch64-unknown-linux-gnu": { - "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/SHA256SUMS", - "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/cpython-3.15.0rc1+20260807-aarch64-unknown-linux-gnu-pgo+lto-full.tar.zst" + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260814/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260814/cpython-3.15.0rc1+20260814-aarch64-unknown-linux-gnu-pgo+lto-full.tar.zst" }, "aarch64-unknown-linux-musl": { - "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/SHA256SUMS", - "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/cpython-3.15.0rc1+20260807-aarch64-unknown-linux-musl-lto-full.tar.zst" + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260814/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260814/cpython-3.15.0rc1+20260814-aarch64-unknown-linux-musl-lto-full.tar.zst" }, "armv7-unknown-linux-gnueabi": { - "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/SHA256SUMS", - "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/cpython-3.15.0rc1+20260807-armv7-unknown-linux-gnueabi-lto-full.tar.zst" + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260814/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260814/cpython-3.15.0rc1+20260814-armv7-unknown-linux-gnueabi-lto-full.tar.zst" }, "armv7-unknown-linux-gnueabihf": { - "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/SHA256SUMS", - "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/cpython-3.15.0rc1+20260807-armv7-unknown-linux-gnueabihf-lto-full.tar.zst" + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260814/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260814/cpython-3.15.0rc1+20260814-armv7-unknown-linux-gnueabihf-lto-full.tar.zst" }, "i686-pc-windows-msvc": { - "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/SHA256SUMS", - "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/cpython-3.15.0rc1+20260807-i686-pc-windows-msvc-pgo-full.tar.zst" + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260814/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260814/cpython-3.15.0rc1+20260814-i686-pc-windows-msvc-pgo-full.tar.zst" }, "powerpc64le-unknown-linux-gnu": { - "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/SHA256SUMS", - "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/cpython-3.15.0rc1+20260807-ppc64le-unknown-linux-gnu-lto-full.tar.zst" + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260814/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260814/cpython-3.15.0rc1+20260814-ppc64le-unknown-linux-gnu-lto-full.tar.zst" }, "riscv64gc-unknown-linux-gnu": { - "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/SHA256SUMS", - "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/cpython-3.15.0rc1+20260807-riscv64-unknown-linux-gnu-lto-full.tar.zst" + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260814/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260814/cpython-3.15.0rc1+20260814-riscv64-unknown-linux-gnu-lto-full.tar.zst" }, "s390x-unknown-linux-gnu": { - "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/SHA256SUMS", - "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/cpython-3.15.0rc1+20260807-s390x-unknown-linux-gnu-lto-full.tar.zst" + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260814/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260814/cpython-3.15.0rc1+20260814-s390x-unknown-linux-gnu-lto-full.tar.zst" }, "x86_64-apple-darwin": { - "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/SHA256SUMS", - "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/cpython-3.15.0rc1+20260807-x86_64-apple-darwin-pgo+lto-full.tar.zst" + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260814/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260814/cpython-3.15.0rc1+20260814-x86_64-apple-darwin-pgo+lto-full.tar.zst" }, "x86_64-pc-windows-msvc": { - "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/SHA256SUMS", - "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/cpython-3.15.0rc1+20260807-x86_64-pc-windows-msvc-pgo-full.tar.zst" + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260814/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260814/cpython-3.15.0rc1+20260814-x86_64-pc-windows-msvc-pgo-full.tar.zst" }, "x86_64-unknown-linux-gnu": { - "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/SHA256SUMS", - "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/cpython-3.15.0rc1+20260807-x86_64-unknown-linux-gnu-pgo+lto-full.tar.zst" + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260814/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260814/cpython-3.15.0rc1+20260814-x86_64-unknown-linux-gnu-pgo+lto-full.tar.zst" }, "x86_64-unknown-linux-musl": { - "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/SHA256SUMS", - "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260807/cpython-3.15.0rc1+20260807-x86_64-unknown-linux-musl-lto-full.tar.zst" + "checksum": "https://github.com/astral-sh/python-build-standalone/releases/download/20260814/SHA256SUMS", + "download": "https://github.com/astral-sh/python-build-standalone/releases/download/20260814/cpython-3.15.0rc1+20260814-x86_64-unknown-linux-musl-lto-full.tar.zst" } }, "3.7.6": { From 97da725a912ea1ae80b91e03fd653f6f31686127 Mon Sep 17 00:00:00 2001 From: Wouter Date: Sun, 26 Jul 2026 19:21:51 +0200 Subject: [PATCH 77/78] feat(dotnet): add .NET toolchain plugin New `dotnet_toolchain` WASM plugin covering tiers 1 through 3 for SDK-style C#, F# and VB projects. - tier1: register_toolchain (csproj/fsproj/vbproj, sln/slnx, global.json, Directory.Build.*, Directory.Packages.props, nuget.config, packages.*.lock.json), define_toolchain_config, initialize_toolchain, define_docker_metadata with restore-layer scaffold globs, prune_docker. - tier2: locate_dependencies_root (nearest solution, then lock file, then project file), install_dependencies (dotnet restore, with --locked-mode when a lock file is present), setup_environment (dotnet tool restore for local tool manifests), extend_task_command (DOTNET_ROOT and PATH), extend_project_graph (dependency and task inference, AssemblyName aliases), parse_lock, parse_manifest, hash_task_contents. - tier3: setup_toolchain, installing the SDK via the official dotnet-install scripts when `version` is configured. Dependencies and tasks come from a real MSBuild evaluation rather than from parsing project XML, so Directory.Build.targets imports, MSBuild properties such as $(SolutionDir), conditional references and Central Package Management resolve the way the SDK resolves them. There is no parser to maintain. Every project in the workspace is evaluated in one batched traversal invocation rather than one process per project, and the evaluated package sets are cached on disk so task hashing reuses them instead of re-evaluating. Registers dotnet-toolchain in .moon/workspace.yml, and adds actions/setup-dotnet to CI because the integration tests evaluate their fixtures with a real dotnet msbuild. --- .github/workflows/ci.yml | 5 + .moon/workspace.yml | 1 + Cargo.lock | 20 + toolchains/dotnet/CHANGELOG.md | 23 + toolchains/dotnet/Cargo.toml | 41 + toolchains/dotnet/src/config.rs | 182 +++ toolchains/dotnet/src/discovery.rs | 175 +++ toolchains/dotnet/src/dotnet_install.rs | 137 ++ toolchains/dotnet/src/eval_cache.rs | 199 +++ toolchains/dotnet/src/global_json.rs | 345 ++++ toolchains/dotnet/src/infer_tasks.rs | 405 +++++ toolchains/dotnet/src/inherited_tasks.rs | 173 ++ toolchains/dotnet/src/lib.rs | 31 + toolchains/dotnet/src/msbuild.rs | 564 +++++++ toolchains/dotnet/src/nuget_lock.rs | 104 ++ toolchains/dotnet/src/project_graph.rs | 552 +++++++ toolchains/dotnet/src/tier1.rs | 119 ++ toolchains/dotnet/src/tier2.rs | 365 +++++ toolchains/dotnet/src/tier2_env.rs | 310 ++++ toolchains/dotnet/src/tier3.rs | 239 +++ .../__fixtures__/cpm/Directory.Packages.props | 8 + .../tests/__fixtures__/cpm/proj/Class1.cs | 3 + .../tests/__fixtures__/cpm/proj/Cpm.csproj | 8 + .../locate-no-sln/proj/Proj.csproj | 5 + .../dotnet/tests/__fixtures__/locate/Root.sln | 2 + .../locate/nested/proj/Proj.csproj | 5 + .../tests/__fixtures__/locked/proj/Class1.cs | 6 + .../__fixtures__/locked/proj/Locked.csproj | 9 + .../locked/proj/packages.lock.json | 13 + .../__fixtures__/matrix/Directory.Build.props | 8 + .../tests/__fixtures__/matrix/cond/Class1.cs | 3 + .../__fixtures__/matrix/cond/Cond.csproj | 11 + .../tests/__fixtures__/matrix/multi/Class1.cs | 5 + .../__fixtures__/matrix/multi/Multi.csproj | 10 + .../matrix/nested/Directory.Build.props | 7 + .../__fixtures__/matrix/nested/deep/Class1.cs | 3 + .../matrix/nested/deep/Deep.csproj | 6 + .../__fixtures__/mixed-lang/app/App.csproj | 10 + .../__fixtures__/mixed-lang/app/Program.cs | 1 + .../__fixtures__/mixed-lang/core/Class1.vb | 2 + .../__fixtures__/mixed-lang/core/Core.vbproj | 6 + .../__fixtures__/mixed-lang/lib/Lib.fsproj | 11 + .../__fixtures__/mixed-lang/lib/Library.fs | 4 + .../dotnet/tests/__fixtures__/mtp/global.json | 5 + .../__fixtures__/mtp/suite/Helpers.csproj | 5 + .../__fixtures__/mtp/suite/Suite.Tests.csproj | 11 + .../tests/__fixtures__/mtp/suite/UnitTest1.cs | 6 + .../projects/app-tests/App.Tests.csproj | 13 + .../projects/app-tests/UnitTest1.cs | 9 + .../__fixtures__/projects/app/App.csproj | 11 + .../__fixtures__/projects/app/Program.cs | 1 + .../__fixtures__/projects/core/Class1.cs | 6 + .../__fixtures__/projects/core/Core.csproj | 6 + .../tests/__fixtures__/projects/lib/Class1.cs | 6 + .../__fixtures__/projects/lib/Lib.csproj | 9 + .../unevaluatable/proj/Broken.csproj | 9 + toolchains/dotnet/tests/infer_tasks_test.rs | 521 ++++++ toolchains/dotnet/tests/msbuild_batch_test.rs | 154 ++ toolchains/dotnet/tests/msbuild_test.rs | 302 ++++ toolchains/dotnet/tests/tier1_test.rs | 147 ++ toolchains/dotnet/tests/tier2_test.rs | 1394 +++++++++++++++++ toolchains/dotnet/tests/tier3_test.rs | 98 ++ 62 files changed, 6849 insertions(+) create mode 100644 toolchains/dotnet/CHANGELOG.md create mode 100644 toolchains/dotnet/Cargo.toml create mode 100644 toolchains/dotnet/src/config.rs create mode 100644 toolchains/dotnet/src/discovery.rs create mode 100644 toolchains/dotnet/src/dotnet_install.rs create mode 100644 toolchains/dotnet/src/eval_cache.rs create mode 100644 toolchains/dotnet/src/global_json.rs create mode 100644 toolchains/dotnet/src/infer_tasks.rs create mode 100644 toolchains/dotnet/src/inherited_tasks.rs create mode 100644 toolchains/dotnet/src/lib.rs create mode 100644 toolchains/dotnet/src/msbuild.rs create mode 100644 toolchains/dotnet/src/nuget_lock.rs create mode 100644 toolchains/dotnet/src/project_graph.rs create mode 100644 toolchains/dotnet/src/tier1.rs create mode 100644 toolchains/dotnet/src/tier2.rs create mode 100644 toolchains/dotnet/src/tier2_env.rs create mode 100644 toolchains/dotnet/src/tier3.rs create mode 100644 toolchains/dotnet/tests/__fixtures__/cpm/Directory.Packages.props create mode 100644 toolchains/dotnet/tests/__fixtures__/cpm/proj/Class1.cs create mode 100644 toolchains/dotnet/tests/__fixtures__/cpm/proj/Cpm.csproj create mode 100644 toolchains/dotnet/tests/__fixtures__/locate-no-sln/proj/Proj.csproj create mode 100644 toolchains/dotnet/tests/__fixtures__/locate/Root.sln create mode 100644 toolchains/dotnet/tests/__fixtures__/locate/nested/proj/Proj.csproj create mode 100644 toolchains/dotnet/tests/__fixtures__/locked/proj/Class1.cs create mode 100644 toolchains/dotnet/tests/__fixtures__/locked/proj/Locked.csproj create mode 100644 toolchains/dotnet/tests/__fixtures__/locked/proj/packages.lock.json create mode 100644 toolchains/dotnet/tests/__fixtures__/matrix/Directory.Build.props create mode 100644 toolchains/dotnet/tests/__fixtures__/matrix/cond/Class1.cs create mode 100644 toolchains/dotnet/tests/__fixtures__/matrix/cond/Cond.csproj create mode 100644 toolchains/dotnet/tests/__fixtures__/matrix/multi/Class1.cs create mode 100644 toolchains/dotnet/tests/__fixtures__/matrix/multi/Multi.csproj create mode 100644 toolchains/dotnet/tests/__fixtures__/matrix/nested/Directory.Build.props create mode 100644 toolchains/dotnet/tests/__fixtures__/matrix/nested/deep/Class1.cs create mode 100644 toolchains/dotnet/tests/__fixtures__/matrix/nested/deep/Deep.csproj create mode 100644 toolchains/dotnet/tests/__fixtures__/mixed-lang/app/App.csproj create mode 100644 toolchains/dotnet/tests/__fixtures__/mixed-lang/app/Program.cs create mode 100644 toolchains/dotnet/tests/__fixtures__/mixed-lang/core/Class1.vb create mode 100644 toolchains/dotnet/tests/__fixtures__/mixed-lang/core/Core.vbproj create mode 100644 toolchains/dotnet/tests/__fixtures__/mixed-lang/lib/Lib.fsproj create mode 100644 toolchains/dotnet/tests/__fixtures__/mixed-lang/lib/Library.fs create mode 100644 toolchains/dotnet/tests/__fixtures__/mtp/global.json create mode 100644 toolchains/dotnet/tests/__fixtures__/mtp/suite/Helpers.csproj create mode 100644 toolchains/dotnet/tests/__fixtures__/mtp/suite/Suite.Tests.csproj create mode 100644 toolchains/dotnet/tests/__fixtures__/mtp/suite/UnitTest1.cs create mode 100644 toolchains/dotnet/tests/__fixtures__/projects/app-tests/App.Tests.csproj create mode 100644 toolchains/dotnet/tests/__fixtures__/projects/app-tests/UnitTest1.cs create mode 100644 toolchains/dotnet/tests/__fixtures__/projects/app/App.csproj create mode 100644 toolchains/dotnet/tests/__fixtures__/projects/app/Program.cs create mode 100644 toolchains/dotnet/tests/__fixtures__/projects/core/Class1.cs create mode 100644 toolchains/dotnet/tests/__fixtures__/projects/core/Core.csproj create mode 100644 toolchains/dotnet/tests/__fixtures__/projects/lib/Class1.cs create mode 100644 toolchains/dotnet/tests/__fixtures__/projects/lib/Lib.csproj create mode 100644 toolchains/dotnet/tests/__fixtures__/unevaluatable/proj/Broken.csproj create mode 100644 toolchains/dotnet/tests/infer_tasks_test.rs create mode 100644 toolchains/dotnet/tests/msbuild_batch_test.rs create mode 100644 toolchains/dotnet/tests/msbuild_test.rs create mode 100644 toolchains/dotnet/tests/tier1_test.rs create mode 100644 toolchains/dotnet/tests/tier2_test.rs create mode 100644 toolchains/dotnet/tests/tier3_test.rs diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 20572347..ccff3098 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -97,6 +97,11 @@ jobs: cache: false # Runs out of disk space components: clippy, rustfmt targets: wasm32-wasip1 + # The dotnet toolchain's tests evaluate fixtures with a real `dotnet + # msbuild`, and `-getProperty`/`-getItem` JSON output requires SDK 8+. + - uses: actions/setup-dotnet@v5 + with: + dotnet-version: '8.0.x' - run: moon ci --color --job ${{ matrix.job }} --job-total ${{ needs.plan.outputs.job-total }} --log debug # env: # DEPOT_TOKEN: ${{ secrets.DEPOT_TOKEN }} diff --git a/.moon/workspace.yml b/.moon/workspace.yml index 9b8c6c88..520acf5e 100644 --- a/.moon/workspace.yml +++ b/.moon/workspace.yml @@ -30,6 +30,7 @@ projects: # Toolchains bun-toolchain: toolchains/bun deno-toolchain: toolchains/deno + dotnet-toolchain: toolchains/dotnet go-toolchain: toolchains/go javascript-toolchain: toolchains/javascript node-toolchain: toolchains/node diff --git a/Cargo.lock b/Cargo.lock index 8bb40ba8..ee1b653e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1428,6 +1428,26 @@ version = "0.15.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" +[[package]] +name = "dotnet_toolchain" +version = "0.1.0" +dependencies = [ + "extism-pdk", + "moon_common", + "moon_config", + "moon_pdk", + "moon_pdk_api", + "moon_pdk_test_utils", + "moon_target", + "schematic", + "serde", + "serde_json", + "starbase_sandbox 0.11.1", + "starbase_utils 0.13.8", + "tokio", + "toolchain_common", +] + [[package]] name = "download_extension" version = "1.0.3" diff --git a/toolchains/dotnet/CHANGELOG.md b/toolchains/dotnet/CHANGELOG.md new file mode 100644 index 00000000..704f6dbc --- /dev/null +++ b/toolchains/dotnet/CHANGELOG.md @@ -0,0 +1,23 @@ +# Changelog + +## 0.1.0 + +#### 🚀 Updates + +- Initial release of the .NET toolchain plugin. + - Tier 1: project and task detection (`*.csproj`/`*.fsproj`/`*.vbproj`, `*.sln`/`*.slnx`, + `global.json`, `Directory.Build.*`, `Directory.Packages.props`, `nuget.config`), + config schema, and Docker metadata with restore-layer scaffold globs. + - Tier 2: dependency root location, `dotnet restore` installs with automatic + `--locked-mode` when a lock file is present, local tool manifest restore + (`.config/dotnet-tools.json`), `packages.lock.json` and `Directory.Packages.props` + parsing, project-graph dependency inference from `ProjectReference`, task inference + (`build`/`test`/`run`/`publish`), `AssemblyName` project aliases, task-content + hashing, and `DOTNET_ROOT`/`PATH` injection into task environments. + - Tier 3: .NET SDK installation via the official dotnet-install scripts when + `version` is configured. +- Dependencies and tasks are inferred from a real MSBuild evaluation rather than by + parsing project XML, so `Directory.Build.targets` imports, MSBuild properties such as + `$(SolutionDir)`, conditional references and Central Package Management all resolve the + way the SDK resolves them. Every project in the workspace is evaluated in a single + batched invocation, and the results are cached on disk for task hashing to reuse. diff --git a/toolchains/dotnet/Cargo.toml b/toolchains/dotnet/Cargo.toml new file mode 100644 index 00000000..7004ae13 --- /dev/null +++ b/toolchains/dotnet/Cargo.toml @@ -0,0 +1,41 @@ +[package] +name = "dotnet_toolchain" +version = "0.1.0" +edition = "2024" +description = ".NET toolchain WASM plugin for moon." +authors = ["Wouter Tiben"] +license = "MIT" +repository = "https://github.com/moonrepo/plugins" +documentation = "https://github.com/moonrepo/plugins/tree/master/toolchains/dotnet" +publish = false + +[package.metadata.release] +pre-release-replacements = [ + { file = "./CHANGELOG.md", search = "Unreleased", replace = "{{version}}" }, +] + +[lib] +crate-type = ["cdylib", "lib"] + +[dependencies] +toolchain_common = { path = "../../crates/toolchain-common" } +extism-pdk = { workspace = true } +moon_common = { workspace = true } +moon_config = { workspace = true } +moon_pdk = { workspace = true, features = ["schematic"] } +moon_pdk_api = { workspace = true } +moon_target = { workspace = true } +schematic = { workspace = true, features = ["config"] } +serde = { workspace = true } +serde_json = { workspace = true } +starbase_utils = { workspace = true, features = ["yaml"] } + +[dev-dependencies] +moon_pdk_test_utils = { workspace = true } +serde_json = { workspace = true } +starbase_sandbox = { workspace = true } +tokio = { workspace = true } + +[features] +default = ["wasm"] +wasm = [] diff --git a/toolchains/dotnet/src/config.rs b/toolchains/dotnet/src/config.rs new file mode 100644 index 00000000..754ccbe2 --- /dev/null +++ b/toolchains/dotnet/src/config.rs @@ -0,0 +1,182 @@ +use moon_pdk_api::config_struct; +use schematic::{Config, Schematic}; + +/// The task names the plugin can infer. +pub const INFERABLE_TASKS: &[&str] = &["build", "test", "run", "publish"]; + +/// Which tasks to infer from evaluated MSBuild properties: a boolean to +/// enable/disable all of them, or an explicit list of task names +/// (`build`, `test`, `run`, `publish`) to infer only those. +#[derive(Clone, Debug, PartialEq, Schematic, serde::Deserialize, serde::Serialize)] +#[serde( + untagged, + expecting = "expected a boolean or a list of task names (build, test, run, publish)" +)] +pub enum InferTasksSetting { + Enabled(bool), + Only(Vec), +} + +impl Default for InferTasksSetting { + fn default() -> Self { + Self::Enabled(true) + } +} + +impl InferTasksSetting { + /// Is a specific task name selected for inference? + pub fn includes(&self, task: &str) -> bool { + match self { + Self::Enabled(enabled) => *enabled, + Self::Only(list) => list.iter().any(|name| name.eq_ignore_ascii_case(task)), + } + } + + /// Is any inference enabled at all? + pub fn any_enabled(&self) -> bool { + match self { + Self::Enabled(enabled) => *enabled, + Self::Only(list) => !list.is_empty(), + } + } +} + +config_struct!( + /// Configures and enables the .NET toolchain. + #[derive(Config)] + pub struct DotnetToolchainConfig { + /// Infer moon project dependencies from MSBuild `ProjectReference` + /// items, so `moon` knows the real build order without any `dependsOn` + /// declarations. + /// + /// This runs a real MSBuild evaluation, which is what makes it see + /// references added by `Directory.Build.targets` and conditional + /// `ProjectReference`s — not just what a project file lists literally. + /// Every project in the workspace is evaluated in a single batched + /// invocation. A reference outside the moon workspace is skipped. + /// + /// Defaults to `true`. + #[setting(default = true)] + pub infer_dependencies: bool, + + /// Infer `build`, `test`, `run` and `publish` tasks from each project's + /// evaluated MSBuild properties. + /// + /// `true` infers all four, `false` infers none, and a list infers only + /// the named ones — `['build', 'test']`. Unrecognised names in the list + /// are ignored rather than rejected. Being workspace-level, one line + /// here covers every project; turning inference off never requires + /// per-project overrides. + /// + /// What gets inferred: + /// + /// - `build` for every project, with `deps: ['^:build']` and + /// `--no-dependencies`, so moon orchestrates and caches the graph + /// per project rather than delegating that to MSBuild. + /// - `test` for a project with `IsTestProject=true` or a + /// `Microsoft.NET.Test.Sdk` reference. Both VSTest and + /// Microsoft.Testing.Platform are supported; the command shape follows + /// whichever the governing `global.json` selects. + /// - `run` for `Exe`/`WinExe`, never cached and excluded from CI. + /// - `publish` for single-target-framework `Exe`/`WinExe`. Multi-TFM + /// projects get none, since `dotnet publish` needs an explicit `-f`. + /// + /// Never inferred: `pack`, `watch`, `clean`, and `restore` — moon models + /// restore as the install-dependencies action instead, which is why the + /// inferred commands all pass `--no-restore`. + /// + /// Your own tasks always win. A task of the same id in a project's + /// `moon.yml` replaces the inferred one outright, and an id defined by an + /// inherited task file (`.moon/tasks.yml`, `.moon/tasks/**/*.yml`) that + /// can apply to dotnet projects is not inferred at all — moon would + /// otherwise merge the two into a broken command. Every such suppression + /// is logged with the id and the file that claimed it. + /// + /// Inferred commands pin the evaluated `Configuration` with `-c`, because + /// `dotnet publish` defaults to Release on .NET 8+ while `build` defaults + /// to Debug, and `--no-build` needs them to agree. Task outputs come from + /// the evaluated `BaseOutputPath`/`PublishDir`, so redirected output + /// locations cache correctly; a path resolving outside the workspace + /// makes the task run uncached rather than cache the wrong directory. + /// + /// Defaults to `true`. + pub infer_tasks: InferTasksSetting, + + /// Additional arguments appended to `dotnet restore`, which moon runs as + /// its install-dependencies action rather than as a task. + /// + /// `--locked-mode` is added automatically when a `packages.lock.json` (or + /// a `packages..lock.json`) is found, so it does not need to be + /// passed here. + pub restore_args: Vec, + + /// Explicit `DOTNET_ROOT`, used both for task environments and for the + /// MSBuild evaluation behind dependency and task inference — the two must + /// agree, or the graph gets evaluated by one SDK while tasks run under + /// another. + /// + /// When unset, resolution falls back to an existing `DOTNET_ROOT` + /// environment variable, then to `~/.dotnet` when it holds a `dotnet` + /// executable *and* an SDK satisfying the workspace's `global.json` pin + /// (a leftover install there is otherwise skipped in favour of the + /// `dotnet` on `PATH`). Set explicitly, it is never second-guessed. + pub dotnet_root: Option, + } +); + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn config_schema_builds() { + let schema = schematic::SchemaBuilder::build_root::(); + let json = serde_json::to_string(&schema).unwrap(); + + assert!(json.contains("inferDependencies")); + assert!(json.contains("inferTasks")); + assert!(json.contains("restoreArgs")); + assert!(json.contains("dotnetRoot")); + + // `inferTasks` must stay a `bool | string[]` union. The derive produces + // this from the untagged enum; asserting the shape means a change to the + // enum cannot silently narrow what the setting accepts. + assert!( + json.contains( + r#""operator":"AnyOf","variants_types":[{"ty":{"type":"Boolean"}},{"ty":{"type":"Array","items_type":{"ty":{"type":"String"}}}}]"# + ), + "inferTasks lost its bool | string[] union: {json}" + ); + } + + #[test] + fn config_defaults_apply() { + let config: DotnetToolchainConfig = serde_json::from_value(serde_json::json!({})).unwrap(); + + assert!(config.infer_dependencies); + assert_eq!(config.infer_tasks, InferTasksSetting::Enabled(true)); + assert!(config.infer_tasks.any_enabled()); + assert!(config.restore_args.is_empty()); + assert!(config.dotnet_root.is_none()); + } + + #[test] + fn infer_tasks_accepts_bool_and_list() { + let config: DotnetToolchainConfig = + serde_json::from_value(serde_json::json!({ "inferTasks": false })).unwrap(); + assert!(!config.infer_tasks.any_enabled()); + assert!(!config.infer_tasks.includes("build")); + + let config: DotnetToolchainConfig = + serde_json::from_value(serde_json::json!({ "inferTasks": ["build", "Test"] })).unwrap(); + assert!(config.infer_tasks.any_enabled()); + assert!(config.infer_tasks.includes("build")); + assert!(config.infer_tasks.includes("test")); + assert!(!config.infer_tasks.includes("run")); + assert!(!config.infer_tasks.includes("publish")); + + let config: DotnetToolchainConfig = + serde_json::from_value(serde_json::json!({ "inferTasks": [] })).unwrap(); + assert!(!config.infer_tasks.any_enabled()); + } +} diff --git a/toolchains/dotnet/src/discovery.rs b/toolchains/dotnet/src/discovery.rs new file mode 100644 index 00000000..1a605bed --- /dev/null +++ b/toolchains/dotnet/src/discovery.rs @@ -0,0 +1,175 @@ +//! Locating the files this toolchain cares about, relative to a directory. +//! +//! The `find_*` helpers enumerate one directory; `walk_up` supplies the +//! workspace-bounded upward traversal they are usually driven by. Neither has a +//! PDK equivalent: `warpgate_pdk` exposes no directory listing at all, and +//! `moon_pdk::locate_root*` walks up without a bound. + +use moon_pdk_api::VirtualPath; +use starbase_utils::fs; + +/// Project file extensions this toolchain understands. +pub const PROJECT_EXTENSIONS: &[&str] = &["csproj", "fsproj", "vbproj"]; + +/// Directories never worth descending into: build output, or owned by another +/// tool. Shared with tier 3's `global.json` scan. +pub const SKIP_DIRS: &[&str] = &["bin", "obj", "node_modules", ".git", ".moon"]; + +/// Workspace-level MSBuild/NuGet config files that can change evaluation, +/// restore, or build behavior from any level between a project dir and the +/// workspace root. Matched case-insensitively: NuGet itself accepts any +/// casing of `nuget.config`, and over-matching the others merely over-hashes +/// (a spurious cache invalidation, never a stale hit). +pub const CONFIG_FILE_NAMES: &[&str] = &[ + "directory.build.props", + "directory.build.rsp", + "directory.build.targets", + "directory.packages.props", + "global.json", + "nuget.config", +]; + +/// Does a file name carry one of the given extensions (case-insensitively)? +fn has_extension(name: &str, extensions: &[&str]) -> bool { + name.rsplit_once('.').is_some_and(|(_, ext)| { + extensions + .iter() + .any(|known| known.eq_ignore_ascii_case(ext)) + }) +} + +/// Files directly inside a directory whose name satisfies `keep`, sorted by +/// name and returned as paths under `dir`. +/// +/// An unreadable directory yields nothing rather than an error. Every caller +/// treats "nothing matched" and "could not look" identically, and propagating +/// would turn a single unreadable subdirectory into a failed +/// `install_dependencies` (via `contains_lockfile`'s recursion) or force the +/// infallible digest and boolean-probe callers to change shape. Note that +/// `fs::read_dir` already maps a missing directory to an empty list, so this +/// only absorbs real I/O failures — permissions, symlink loops, WASI +/// `NotCapable`. +fn list_files(dir: &VirtualPath, keep: impl Fn(&str) -> bool) -> Vec { + let Ok(entries) = fs::read_dir(dir.any_path()) else { + return vec![]; + }; + + let mut names = entries + .into_iter() + .filter(|entry| entry.file_type().is_ok_and(|kind| kind.is_file())) + .filter_map(|entry| entry.file_name().into_string().ok()) + .filter(|name| keep(name)) + .collect::>(); + + names.sort(); + + names.into_iter().map(|name| dir.join(name)).collect() +} + +/// Names of the subdirectories directly inside a directory. Same +/// unreadable-yields-nothing contract as [`list_files`]. +fn list_dirs(dir: &VirtualPath) -> Vec { + let Ok(entries) = fs::read_dir(dir.any_path()) else { + return vec![]; + }; + + entries + .into_iter() + .filter(|entry| entry.file_type().is_ok_and(|kind| kind.is_dir())) + .filter_map(|entry| entry.file_name().into_string().ok()) + .collect() +} + +/// List MSBuild project files (*.csproj etc.) directly inside a directory +/// (non-recursive). +pub fn find_project_files(dir: &VirtualPath) -> Vec { + list_files(dir, |name| has_extension(name, PROJECT_EXTENSIONS)) +} + +/// NuGet lock file names: the default `packages.lock.json`, plus the +/// `packages..lock.json` convention used when `NuGetLockFilePath` +/// renames it (case-insensitive, NuGet accepts any casing). +pub fn is_lock_file_name(name: &str) -> bool { + let lower = name.to_ascii_lowercase(); + + // The default name needs no special case: it starts with `packages.` and + // ends with `.lock.json` like the renamed variants. + lower.starts_with("packages.") && lower.ends_with(".lock.json") +} + +/// List NuGet lock files directly inside a directory (non-recursive), sorted. +pub fn find_lock_files(dir: &VirtualPath) -> Vec { + list_files(dir, is_lock_file_name) +} + +/// List hash-relevant config files directly inside a directory +/// (non-recursive), sorted by actual file name. +pub fn find_config_files(dir: &VirtualPath) -> Vec { + list_files(dir, |name| { + CONFIG_FILE_NAMES.contains(&name.to_ascii_lowercase().as_str()) + }) +} + +/// Does a directory directly contain a solution file (*.sln / *.slnx)? +pub fn has_solution_file(dir: &VirtualPath) -> bool { + !list_files(dir, |name| has_extension(name, &["sln", "slnx"])).is_empty() +} + +/// How far below a dependencies root to look for a lock file. Lock files sit +/// next to each project file rather than at the root, and .NET repositories +/// conventionally nest them a few levels down (`src///`). +pub const LOCKFILE_SEARCH_DEPTH: u8 = 5; + +/// Depth-limited search for any NuGet lock file under a directory. +/// Lock files live next to each project file, not at the dependencies root, +/// so a root-only check would miss them. +pub fn contains_lockfile(dir: &VirtualPath, depth: u8) -> bool { + if !find_lock_files(dir).is_empty() { + return true; + } + + if depth == 0 { + return false; + } + + list_dirs(dir) + .into_iter() + .filter(|name| !SKIP_DIRS.iter().any(|skip| skip.eq_ignore_ascii_case(name))) + .any(|name| contains_lockfile(&dir.join(name), depth - 1)) +} + +/// SDK versions laid out under a .NET root (`/sdk/`). +pub fn installed_sdk_versions(root: &VirtualPath) -> Vec { + list_dirs(&root.join("sdk")) +} + +/// Directories from `start` up to and including `workspace_root`. +/// +/// Every upward search in this plugin is bounded by the workspace root, which is +/// why moon's own `locate_root*` helpers are not used: they are unbounded, so +/// `global.json` or `dotnet-tools.json` discovery could escape into `$HOME` or a +/// parent repository and pick up a file that governs nothing here. The bound +/// matters most for `VirtualPath::Real`, whose `parent()` keeps yielding host +/// directories all the way to the filesystem root. +/// +/// Stops early if `start` is not under `workspace_root`, once `parent()` runs +/// out. +pub fn walk_up( + start: &VirtualPath, + workspace_root: &VirtualPath, +) -> impl Iterator { + let root = workspace_root.any_path().to_owned(); + let mut next = Some(start.to_owned()); + + std::iter::from_fn(move || { + let dir = next.take()?; + + next = if dir.any_path() == &root { + None + } else { + dir.parent() + }; + + Some(dir) + }) +} diff --git a/toolchains/dotnet/src/dotnet_install.rs b/toolchains/dotnet/src/dotnet_install.rs new file mode 100644 index 00000000..3802f9e5 --- /dev/null +++ b/toolchains/dotnet/src/dotnet_install.rs @@ -0,0 +1,137 @@ +use moon_config::UnresolvedVersionSpec; + +/// Official Microsoft install-script endpoints (stable redirect aliases). +pub const INSTALL_SCRIPT_URL_PS1: &str = "https://dot.net/v1/dotnet-install.ps1"; +pub const INSTALL_SCRIPT_URL_SH: &str = "https://dot.net/v1/dotnet-install.sh"; + +pub fn install_script_url(windows: bool) -> &'static str { + if windows { + INSTALL_SCRIPT_URL_PS1 + } else { + INSTALL_SCRIPT_URL_SH + } +} + +pub fn install_script_file_name(windows: bool) -> &'static str { + if windows { + "dotnet-install.ps1" + } else { + "dotnet-install.sh" + } +} + +/// The exact SDK version an exact spec would install (`8.0.404`), used to +/// short-circuit when `/sdk/` already exists. Channels and +/// aliases resolve server-side, so only fully-qualified versions qualify. +pub fn exact_version(spec: &UnresolvedVersionSpec) -> Option { + match spec { + UnresolvedVersionSpec::Semantic(version) => Some(version.to_string()), + _ => None, + } +} + +/// Map a configured version spec onto dotnet-install script arguments, +/// passing through the script's native semantics: `X.Y` requirements become +/// channels, `lts`/`sts`/`preview` aliases become named channels, and +/// fully-qualified versions install pinned. +pub fn install_version_args( + spec: &UnresolvedVersionSpec, + windows: bool, +) -> Result, String> { + let channel_flag = if windows { "-Channel" } else { "--channel" }; + let version_flag = if windows { "-Version" } else { "--version" }; + + let unsupported = |value: &dyn std::fmt::Display| { + format!( + "Unsupported .NET version specification `{value}` — use a channel like `8.0`, \ + an exact version like `8.0.404`, or one of `lts`, `sts`, `preview`." + ) + }; + + match spec { + UnresolvedVersionSpec::Semantic(version) => { + Ok(vec![version_flag.into(), version.to_string()]) + } + UnresolvedVersionSpec::Alias(alias) => { + let channel = match alias.to_ascii_lowercase().as_str() { + "lts" => "LTS", + // "Current" was renamed to STS; treat "latest" the same way. + "sts" | "current" | "latest" => "STS", + "preview" => "Preview", + _ => return Err(unsupported(alias)), + }; + + Ok(vec![channel_flag.into(), channel.into()]) + } + UnresolvedVersionSpec::Req(req) => { + let Some(comparator) = req.comparators.first() else { + return Err(unsupported(req)); + }; + + // dotnet-install channels are `major.minor` feature bands. + Ok(vec![ + channel_flag.into(), + format!("{}.{}", comparator.major, comparator.minor.unwrap_or(0)), + ]) + } + other => Err(unsupported(other)), + } +} + +#[cfg(test)] +mod tests { + use super::*; + + fn parse(value: &str) -> UnresolvedVersionSpec { + UnresolvedVersionSpec::parse(value).unwrap() + } + + #[test] + fn exact_version_only_for_fully_qualified() { + assert_eq!(exact_version(&parse("8.0.404")).as_deref(), Some("8.0.404")); + assert_eq!(exact_version(&parse("8.0")), None); + assert_eq!(exact_version(&parse("lts")), None); + } + + #[test] + fn exact_versions_pass_through() { + assert_eq!( + install_version_args(&parse("8.0.404"), true).unwrap(), + vec!["-Version", "8.0.404"] + ); + assert_eq!( + install_version_args(&parse("8.0.404"), false).unwrap(), + vec!["--version", "8.0.404"] + ); + } + + #[test] + fn partial_versions_become_channels() { + assert_eq!( + install_version_args(&parse("8.0"), true).unwrap(), + vec!["-Channel", "8.0"] + ); + assert_eq!( + install_version_args(&parse("9"), false).unwrap(), + vec!["--channel", "9.0"] + ); + } + + #[test] + fn aliases_become_named_channels() { + assert_eq!( + install_version_args(&parse("lts"), false).unwrap(), + vec!["--channel", "LTS"] + ); + assert_eq!( + install_version_args(&parse("latest"), true).unwrap(), + vec!["-Channel", "STS"] + ); + } + + #[test] + fn unsupported_specs_error() { + assert!(install_version_args(&parse("canary"), true).is_err()); + assert!(install_version_args(&parse("banana"), false).is_err()); + } +} diff --git a/toolchains/dotnet/src/eval_cache.rs b/toolchains/dotnet/src/eval_cache.rs new file mode 100644 index 00000000..350639ad --- /dev/null +++ b/toolchains/dotnet/src/eval_cache.rs @@ -0,0 +1,199 @@ +//! On-disk cache of evaluated NuGet package sets, keyed per moon project. +//! +//! Task hashing needs the same data the project graph just evaluated, but runs +//! later — often in a separate process, against an already-cached project graph +//! — so it cannot rely on in-memory state. Without this, a workspace with no +//! lock files pays one MSBuild evaluation *per project* while hashing, which is +//! exactly what batching the graph evaluation exists to avoid. + +use crate::discovery::{find_config_files, find_project_files, walk_up}; +use moon_pdk_api::VirtualPath; +use serde::{Deserialize, Serialize}; +use starbase_utils::fs; +use std::collections::BTreeMap; + +/// FNV-1a digest, rendered hex. Used only to discriminate cache keys, never +/// for integrity — a plain content hash would mean pulling sha2 into the +/// wasm binary. Deterministic across Rust versions, unlike `DefaultHasher`. +pub fn content_digest(content: &str) -> String { + let mut hash: u64 = 0xcbf2_9ce4_8422_2325; + + for byte in content.as_bytes() { + hash ^= *byte as u64; + hash = hash.wrapping_mul(0x0000_0100_0000_01b3); + } + + format!("{hash:016x}") +} + +/// Cached evaluated package set for one moon project, written by the batched +/// graph evaluation and read back by task hashing. +#[derive(Debug, Deserialize, Serialize)] +struct EvalCacheEntry { + /// Digest of every file that can change the evaluated package set, so a + /// stale entry is never used. + digest: String, + packages: BTreeMap, +} + +/// Where cached package sets live. Under `.moon/cache`, which moon already +/// treats as disposable. +fn eval_cache_file(workspace_root: &VirtualPath, project_id: &str) -> VirtualPath { + let safe_id = project_id + .chars() + .map(|char| { + if char.is_ascii_alphanumeric() || matches!(char, '-' | '_' | '.') { + char + } else { + '_' + } + }) + .collect::(); + + workspace_root + .join(".moon") + .join("cache") + .join("dotnet-toolchain") + .join("eval") + .join(format!("{safe_id}.json")) +} + +/// Append one file to the digest buffer, framed by its name and byte length. +/// +/// Framing is load-bearing. Concatenating contents directly means the same bytes +/// distributed differently across two files produce the same buffer: moving a +/// `` block from the end of `Directory.Build.props` to the start +/// of `Directory.Packages.props` — adjacent in `find_config_files`' name sort — +/// is a routine Central Package Management migration, and it left the digest, and +/// therefore every task hash, unchanged. +/// +/// The file *name* is used rather than the full path, so the digest stays +/// independent of where the workspace lives on disk. +/// +/// An unreadable file contributes an empty body rather than propagating: that +/// yields a digest that cannot match the one recorded when the file was +/// readable, i.e. a cache miss, which is the safe direction. Returning an error +/// instead would break `write_eval_cache`'s best-effort contract. +fn push_framed(buffer: &mut String, file: &VirtualPath) { + let content = fs::read_file(file).unwrap_or_default(); + let name = file + .file_name() + .map(|name| name.to_string_lossy()) + .unwrap_or_default(); + + buffer.push_str(&name); + buffer.push(':'); + buffer.push_str(&content.len().to_string()); + buffer.push(':'); + buffer.push_str(&content); +} + +/// Digest of everything that can change a project's evaluated package set: +/// its project files, plus every config file from the project directory up to +/// the workspace root. +/// +/// Two things are deliberately *not* captured. Custom ``s outside the +/// `Directory.Build.*` conventions — the same caveat that already applies to +/// task hashing itself. And the identity of the SDK that produced the set, so +/// switching `dotnetRoot` or upgrading the system SDK reuses the old answer +/// until some file changes. Including it would mean resolving the SDK root +/// before the cache *read* in `hash_task_contents`, and any asymmetry between +/// the read and write keys turns the cache into a permanent miss — which is the +/// per-project-evaluation cost this cache exists to avoid. +fn eval_cache_digest(project_root: &VirtualPath, workspace_root: &VirtualPath) -> String { + let mut buffer = String::new(); + + for file in find_project_files(project_root) { + push_framed(&mut buffer, &file); + } + + for dir in walk_up(project_root, workspace_root) { + for file in find_config_files(&dir) { + push_framed(&mut buffer, &file); + } + } + + content_digest(&buffer) +} + +/// Persist a project's evaluated package set for task hashing to reuse. +/// +/// Callers must only pass a set they evaluated *completely*: a partial set is +/// indistinguishable from a complete one once written, and it would be served +/// under a digest that keeps validating. +pub fn write_eval_cache( + workspace_root: &VirtualPath, + project_id: &str, + project_root: &VirtualPath, + packages: BTreeMap, +) { + let file = eval_cache_file(workspace_root, project_id); + + let entry = EvalCacheEntry { + digest: eval_cache_digest(project_root, workspace_root), + packages, + }; + + // Best-effort: a failed write only costs a re-evaluation later. Two tasks + // of the same project can race here, but they write identical content and + // a torn read simply fails to parse (also a re-evaluation). + if let Some(parent) = file.parent() { + let _ = fs::create_dir_all(parent); + } + + if let Ok(json) = serde_json::to_string(&entry) { + let _ = fs::write_file(&file, json); + } +} + +/// Read a project's cached package set, if it is still current. +pub fn read_eval_cache( + workspace_root: &VirtualPath, + project_id: &str, + project_root: &VirtualPath, +) -> Option> { + let file = eval_cache_file(workspace_root, project_id); + + if !file.exists() { + return None; + } + + let entry: EvalCacheEntry = serde_json::from_str(&fs::read_file(&file).ok()?).ok()?; + + (entry.digest == eval_cache_digest(project_root, workspace_root)).then_some(entry.packages) +} + +#[cfg(test)] +mod tests { + use super::*; + use starbase_sandbox::create_empty_sandbox; + + #[test] + fn framing_distinguishes_content_moved_between_config_files() { + // The collision being guarded against: these two distributions of the + // same bytes are indistinguishable once concatenated, and + // `find_config_files` sorts these two file names adjacently. + assert_eq!( + content_digest(&format!("{}{}", "", "")), + content_digest(&format!("{}{}", "", "")), + ); + + let sandbox = create_empty_sandbox(); + let root = VirtualPath::Real(sandbox.path().into()); + + sandbox.create_file("Directory.Build.props", ""); + sandbox.create_file("Directory.Packages.props", ""); + + let before = eval_cache_digest(&root, &root); + + // A routine CPM migration: move the declaration to the other file. + sandbox.create_file("Directory.Build.props", ""); + sandbox.create_file("Directory.Packages.props", ""); + + assert_ne!( + before, + eval_cache_digest(&root, &root), + "moving a declaration between config files must change the digest" + ); + } +} diff --git a/toolchains/dotnet/src/global_json.rs b/toolchains/dotnet/src/global_json.rs new file mode 100644 index 00000000..c1649376 --- /dev/null +++ b/toolchains/dotnet/src/global_json.rs @@ -0,0 +1,345 @@ +//! `global.json` SDK pin parsing. +//! +//! Used to avoid injecting a `DOTNET_ROOT` that cannot satisfy the SDK a +//! workspace pins: the dotnet host resolves `global.json` from the current +//! directory, so a stale root (e.g. a leftover `~/.dotnet`) makes every task +//! fail with the host's own "compatible SDK was not found" error while +//! graph evaluation, running elsewhere, succeeds. + +use serde::Deserialize; + +#[derive(Debug, Default, Deserialize)] +#[serde(default, rename_all = "camelCase")] +struct GlobalJsonFile { + sdk: Option, + test: Option, +} + +#[derive(Debug, Default, Deserialize)] +#[serde(default, rename_all = "camelCase")] +struct GlobalJsonTest { + runner: Option, +} + +#[derive(Debug, Default, Deserialize)] +#[serde(default, rename_all = "camelCase")] +struct GlobalJsonSdk { + version: Option, + roll_forward: Option, + allow_prerelease: Option, +} + +/// How far the dotnet host may roll forward from the pinned SDK version. +/// +/// The `latest*` variants differ from their plain counterparts only in *which* +/// matching SDK gets chosen, not in whether one exists, so both map to the +/// same level here — this type only answers "could any installed SDK satisfy +/// this pin?". +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum RollForward { + Disable, + Patch, + Feature, + Minor, + Major, +} + +impl RollForward { + fn parse(value: Option<&str>) -> Self { + match value.map(str::to_ascii_lowercase).as_deref() { + Some("disable") => Self::Disable, + // `patch` is the documented default when rollForward is unset. + None | Some("patch") | Some("latestpatch") => Self::Patch, + Some("feature") | Some("latestfeature") => Self::Feature, + Some("minor") | Some("latestminor") => Self::Minor, + Some("major") | Some("latestmajor") => Self::Major, + // Unknown values: assume the most permissive level rather than + // wrongly reporting a pin as unsatisfiable. + Some(_) => Self::Major, + } + } +} + +/// An SDK version pinned or installed, as `major.minor.patch` plus whether it +/// carries a prerelease suffix. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct SdkVersion { + pub major: u64, + pub minor: u64, + pub patch: u64, + pub prerelease: bool, +} + +impl SdkVersion { + /// SDK feature band — the hundreds component of the patch (e.g. `201` + /// is band 2). `patch`-level roll-forward stays within one band. + fn feature_band(&self) -> u64 { + self.patch / 100 + } + + fn triple(&self) -> (u64, u64, u64) { + (self.major, self.minor, self.patch) + } + + pub fn parse(value: &str) -> Option { + let value = value.trim(); + let (numeric, prerelease) = match value.split_once('-') { + Some((numeric, _)) => (numeric, true), + None => (value, false), + }; + + let mut parts = numeric.split('.'); + let major = parts.next()?.parse().ok()?; + + // Missing components default to 0, so a lenient "10" or "10.0" pin + // still compares sensibly even though global.json requires all three. + let component = |part: Option<&str>| match part { + Some(value) => value.parse().ok(), + None => Some(0), + }; + + Some(Self { + major, + minor: component(parts.next())?, + patch: component(parts.next())?, + prerelease, + }) + } +} + +/// The SDK pin declared by a `global.json`. +#[derive(Clone, Debug)] +pub struct SdkRequirement { + /// Version string exactly as written, for error messages and + /// `rollForward: disable` comparisons. + pub version: String, + pub parsed: SdkVersion, + pub roll_forward: RollForward, + pub allow_prerelease: bool, +} + +/// Parse the `sdk` block of a `global.json`. Returns `None` when the file +/// declares no SDK version — there is nothing to satisfy then. +pub fn parse_sdk_requirement(content: &str) -> Option { + let file: GlobalJsonFile = serde_json::from_str(content).ok()?; + let sdk = file.sdk?; + let version = sdk.version?; + let parsed = SdkVersion::parse(&version)?; + + Some(SdkRequirement { + version, + parsed, + roll_forward: RollForward::parse(sdk.roll_forward.as_deref()), + // Documented default is to allow prerelease SDKs. + allow_prerelease: sdk.allow_prerelease.unwrap_or(true), + }) +} + +/// Does this `global.json` select Microsoft.Testing.Platform as the runner +/// for `dotnet test` (`{"test": {"runner": "Microsoft.Testing.Platform"}}`)? +/// +/// It changes the `dotnet test` command line, not just the runner: MTP takes +/// the project through `--project` and rejects a positional path, while +/// classic VSTest mode is the exact opposite. Verified against SDK 10.0.201. +pub fn selects_test_platform(content: &str) -> bool { + serde_json::from_str::(content) + .ok() + .and_then(|file| file.test?.runner) + .is_some_and(|runner| runner.eq_ignore_ascii_case("Microsoft.Testing.Platform")) +} + +/// Could any of these installed SDK versions satisfy the pin? +/// +/// Unparseable installed version strings are ignored; an unparseable pin +/// never reaches here (`parse_sdk_requirement` returns `None`). +pub fn satisfies(installed: &[String], requirement: &SdkRequirement) -> bool { + installed.iter().any(|version| { + if requirement.roll_forward == RollForward::Disable { + return version.trim() == requirement.version.trim(); + } + + let Some(candidate) = SdkVersion::parse(version) else { + return false; + }; + + if candidate.prerelease && !requirement.allow_prerelease { + return false; + } + + let pinned = requirement.parsed; + + match requirement.roll_forward { + RollForward::Disable => unreachable!("handled above"), + RollForward::Patch => { + candidate.major == pinned.major + && candidate.minor == pinned.minor + && candidate.feature_band() == pinned.feature_band() + && candidate.patch >= pinned.patch + } + RollForward::Feature => { + candidate.major == pinned.major + && candidate.minor == pinned.minor + && candidate.patch >= pinned.patch + } + RollForward::Minor => { + candidate.major == pinned.major && candidate.triple() >= pinned.triple() + } + RollForward::Major => candidate.triple() >= pinned.triple(), + } + }) +} + +#[cfg(test)] +mod tests { + use super::*; + + fn versions(list: &[&str]) -> Vec { + list.iter().map(|value| value.to_string()).collect() + } + + #[test] + fn parses_versions_with_and_without_prerelease() { + let version = SdkVersion::parse("10.0.201").unwrap(); + assert_eq!(version.triple(), (10, 0, 201)); + assert!(!version.prerelease); + assert_eq!(version.feature_band(), 2); + + let prerelease = SdkVersion::parse("10.0.100-rc.1.25451.107").unwrap(); + assert_eq!(prerelease.triple(), (10, 0, 100)); + assert!(prerelease.prerelease); + + // Lenient about missing components. + assert_eq!(SdkVersion::parse("8").unwrap().triple(), (8, 0, 0)); + assert_eq!(SdkVersion::parse("8.0").unwrap().triple(), (8, 0, 0)); + assert!(SdkVersion::parse("").is_none()); + assert!(SdkVersion::parse("latest").is_none()); + } + + #[test] + fn parses_a_global_json_carrying_unrelated_keys() { + // Shape taken from a production repository: `msbuild-sdks` and `test` + // sit alongside `sdk`, and neither must interfere with the pin. + let requirement = parse_sdk_requirement( + r#"{ + "sdk": { + "version": "10.0.301", + "rollForward": "latestMajor", + "allowPrerelease": true + }, + "msbuild-sdks": { "Aspire.AppHost.Sdk": "13.4.6" }, + "test": { "runner": "Microsoft.Testing.Platform" } + }"#, + ) + .unwrap(); + + assert_eq!(requirement.version, "10.0.301"); + assert_eq!(requirement.roll_forward, RollForward::Major); + assert!(requirement.allow_prerelease); + } + + #[test] + fn detects_the_microsoft_testing_platform_runner() { + // Real shape from a production repo. + assert!(selects_test_platform( + r#"{ + "sdk": { "version": "10.0.301", "rollForward": "latestMajor" }, + "test": { "runner": "Microsoft.Testing.Platform" } + }"# + )); + assert!(selects_test_platform( + r#"{"test":{"runner":"microsoft.testing.platform"}}"# + )); + + assert!(!selects_test_platform(r#"{"test":{"runner":"VSTest"}}"#)); + assert!(!selects_test_platform(r#"{"test":{}}"#)); + assert!(!selects_test_platform(r#"{"sdk":{"version":"10.0.301"}}"#)); + assert!(!selects_test_platform("{}")); + assert!(!selects_test_platform("not json")); + } + + #[test] + fn no_requirement_without_a_version() { + assert!(parse_sdk_requirement("{}").is_none()); + assert!(parse_sdk_requirement(r#"{"sdk":{}}"#).is_none()); + assert!(parse_sdk_requirement(r#"{"sdk":{"rollForward":"major"}}"#).is_none()); + assert!(parse_sdk_requirement("not json").is_none()); + } + + #[test] + fn latest_major_accepts_any_newer_sdk() { + let requirement = + parse_sdk_requirement(r#"{"sdk":{"version":"10.0.301","rollForward":"latestMajor"}}"#) + .unwrap(); + + // The case this guard exists for: a leftover `~/.dotnet` holding only + // SDK 8 cannot serve a 10.x pin, so it must not be preferred over the + // `dotnet` on PATH. + assert!(!satisfies(&versions(&["8.0.423"]), &requirement)); + assert!(satisfies(&versions(&["8.0.423", "10.0.301"]), &requirement)); + assert!(satisfies(&versions(&["11.0.100"]), &requirement)); + assert!(!satisfies(&versions(&["10.0.201"]), &requirement)); + } + + #[test] + fn default_roll_forward_stays_within_the_feature_band() { + let requirement = parse_sdk_requirement(r#"{"sdk":{"version":"10.0.201"}}"#).unwrap(); + + assert_eq!(requirement.roll_forward, RollForward::Patch); + assert!(satisfies(&versions(&["10.0.201"]), &requirement)); + assert!(satisfies(&versions(&["10.0.204"]), &requirement)); + // Higher feature band (3xx) and a different minor are out of reach. + assert!(!satisfies(&versions(&["10.0.301"]), &requirement)); + assert!(!satisfies(&versions(&["10.1.201"]), &requirement)); + assert!(!satisfies(&versions(&["10.0.104"]), &requirement)); + } + + #[test] + fn feature_and_minor_levels_widen_progressively() { + let feature = + parse_sdk_requirement(r#"{"sdk":{"version":"10.0.201","rollForward":"feature"}}"#) + .unwrap(); + assert!(satisfies(&versions(&["10.0.301"]), &feature)); + assert!(!satisfies(&versions(&["10.1.100"]), &feature)); + + let minor = + parse_sdk_requirement(r#"{"sdk":{"version":"10.0.201","rollForward":"latestMinor"}}"#) + .unwrap(); + assert!(satisfies(&versions(&["10.1.100"]), &minor)); + assert!(!satisfies(&versions(&["11.0.100"]), &minor)); + } + + #[test] + fn disable_requires_an_exact_match() { + let requirement = + parse_sdk_requirement(r#"{"sdk":{"version":"10.0.201","rollForward":"disable"}}"#) + .unwrap(); + + assert!(satisfies(&versions(&["10.0.201"]), &requirement)); + assert!(!satisfies(&versions(&["10.0.202"]), &requirement)); + } + + #[test] + fn prerelease_sdks_only_count_when_allowed() { + let allowed = parse_sdk_requirement( + r#"{"sdk":{"version":"10.0.100","rollForward":"latestMajor","allowPrerelease":true}}"#, + ) + .unwrap(); + assert!(satisfies(&versions(&["10.0.100-rc.1.25451.107"]), &allowed)); + + let denied = parse_sdk_requirement( + r#"{"sdk":{"version":"10.0.100","rollForward":"latestMajor","allowPrerelease":false}}"#, + ) + .unwrap(); + assert!(!satisfies(&versions(&["10.0.100-rc.1.25451.107"]), &denied)); + } + + #[test] + fn unknown_roll_forward_values_stay_permissive() { + let requirement = parse_sdk_requirement( + r#"{"sdk":{"version":"8.0.100","rollForward":"someFutureMode"}}"#, + ) + .unwrap(); + + assert!(satisfies(&versions(&["10.0.201"]), &requirement)); + } +} diff --git a/toolchains/dotnet/src/infer_tasks.rs b/toolchains/dotnet/src/infer_tasks.rs new file mode 100644 index 00000000..40dc6e02 --- /dev/null +++ b/toolchains/dotnet/src/infer_tasks.rs @@ -0,0 +1,405 @@ +use crate::config::{INFERABLE_TASKS, InferTasksSetting}; +use crate::msbuild::MsbuildEvaluation; +use moon_common::Id; +use moon_config::{ + Input, Output, PartialTaskArgs, PartialTaskConfig, PartialTaskDependency, + PartialTaskDependencyConfig, PartialTaskOptionsConfig, TaskOptionCache, TaskOptionRunInCI, +}; +use moon_pdk_api::{AnyResult, anyhow}; +use moon_target::Target; +use std::collections::{BTreeMap, BTreeSet}; + +/// Everything task inference needs to know about one MSBuild project. +pub struct InferInputs<'a> { + pub evaluation: &'a MsbuildEvaluation, + + /// Project file name to pass explicitly in commands when the project + /// directory holds more than one MSBuild project file (bare `dotnet + /// build` would otherwise error on ambiguity). + pub explicit_project_file: Option<&'a str>, + + /// Host-real absolute path of the project directory (for making + /// evaluated output paths project-relative). Forward or back slashes. + pub project_dir: &'a str, + + /// Host-real absolute path of the workspace root. + pub workspace_dir: &'a str, + + /// Whether the `global.json` governing this project selects + /// Microsoft.Testing.Platform for `dotnet test`. A project can also opt + /// in on its own via `TestingPlatformDotnetTestSupport`, which is read + /// from the evaluation. + pub test_platform_runner: bool, +} + +/// Strip `base` (plus one separator) from the start of `value`, +/// case-insensitively — Windows paths are case-insensitive and MSBuild +/// output casing is not guaranteed to match what moon reports. Both inputs +/// must already use forward slashes. +fn strip_prefix_ci<'a>(value: &'a str, base: &str) -> Option<&'a str> { + if base.is_empty() { + return None; + } + + let mut value_iter = value.char_indices(); + let mut base_iter = base.chars(); + + loop { + let Some(base_ch) = base_iter.next() else { + // Base fully consumed: the next value char must be the separator. + return match value_iter.next() { + Some((index, '/')) => Some(&value[index + 1..]), + _ => None, + }; + }; + + let (_, value_ch) = value_iter.next()?; + + if value_ch != base_ch && !value_ch.to_lowercase().eq(base_ch.to_lowercase()) { + return None; + } + } +} + +/// Does a package identity mark its project as a test project? +/// +/// Matching is exact or by prefix, never a substring search: real test projects +/// commonly reference `Microsoft.AspNetCore.Mvc.Testing` and +/// `Microsoft.AspNetCore.TestHost`, and plenty of non-test libraries reference +/// helpers with "test" in the name. Those must not qualify on their own. +/// +/// The prefixes cover the families that replaced `Microsoft.NET.Test.Sdk` under +/// Microsoft.Testing.Platform, where that package is absent entirely — `xunit.v3` +/// ships as `xunit.v3`, `xunit.v3.core`, `xunit.v3.mtp-v2` and more, so the whole +/// family is matched rather than enumerated. +fn is_test_package(name: &str) -> bool { + const EXACT: &[&str] = &[ + "microsoft.net.test.sdk", + "mstest", + "mstest.testframework", + "nunit3testadapter", + "tunit", + ]; + + const PREFIXES: &[&str] = &["xunit.v3", "microsoft.testing.platform", "tunit."]; + + let lower = name.to_ascii_lowercase(); + + EXACT.contains(&lower.as_str()) || PREFIXES.iter().any(|prefix| lower.starts_with(prefix)) +} + +/// Turn an evaluated MSBuild output path into a moon task output: relative +/// paths pass through, absolute paths under the project dir become +/// project-relative, absolute paths under the workspace root become +/// workspace-relative (leading `/`). Anything else (redirected outside the +/// workspace) is `None` — the task must then disable caching rather than +/// cache the wrong directory. +pub fn resolve_output_path(raw: &str, project_dir: &str, workspace_dir: &str) -> Option { + if raw.is_empty() { + return None; + } + + let value = raw.replace('\\', "/"); + let value = value.trim_end_matches('/'); + + if value.is_empty() { + return None; + } + + let is_absolute = value.starts_with('/') || value.as_bytes().get(1) == Some(&b':'); + + if !is_absolute { + return Some(value.to_string()); + } + + let project_dir = project_dir.replace('\\', "/"); + + if let Some(relative) = strip_prefix_ci(value, project_dir.trim_end_matches('/')) { + return Some(relative.to_string()); + } + + let workspace_dir = workspace_dir.replace('\\', "/"); + + if let Some(relative) = strip_prefix_ci(value, workspace_dir.trim_end_matches('/')) { + return Some(format!("/{relative}")); + } + + None +} + +fn command(verb: &str, project_file: Option<&str>, extra_args: &[&str]) -> PartialTaskArgs { + let mut list = vec!["dotnet".to_string(), verb.to_string()]; + + list.extend(project_file.map(str::to_string)); + list.extend(extra_args.iter().map(|arg| arg.to_string())); + + PartialTaskArgs::List(list) +} + +/// Pin the evaluated `Configuration` on cacheable commands. `dotnet build` +/// defaults to Debug but `dotnet publish` defaults to Release (.NET 8+), so +/// without an explicit `-c` a `publish --no-build` would look for outputs a +/// `build` never produced. Passing the configuration the evaluation itself +/// saw keeps every command consistent with the evaluated output paths — +/// including repos that set `Configuration` in `Directory.Build.props`. +fn pin_configuration(command: &mut PartialTaskArgs, configuration: &str) { + if configuration.is_empty() { + return; + } + + if let PartialTaskArgs::List(list) = command { + list.push("-c".into()); + list.push(configuration.into()); + } +} + +fn parse_target(target: &str) -> AnyResult { + Ok(PartialTaskDependency::Target( + Target::parse(target).map_err(|error| anyhow!("{error}"))?, + )) +} + +/// Same, but tolerated when the target does not exist. Required for `~:` deps: +/// moon defaults `optional` to `false` for the `OwnSelf` scope, so a project +/// that infers `test` or `publish` without `build` — `inferTasks: ['test']`, or +/// a `build` id claimed by an inherited task file — would fail project-graph +/// construction outright with `UnknownDepTarget` rather than simply losing the +/// ordering edge. +fn parse_optional_target(target: &str) -> AnyResult { + Ok(PartialTaskDependency::Object(PartialTaskDependencyConfig { + target: Some(Target::parse(target).map_err(|error| anyhow!("{error}"))?), + optional: Some(true), + ..Default::default() + })) +} + +/// Inputs for cacheable tasks: everything in the project EXCEPT the +/// evaluated output and intermediate directories. moon's default `**/*` +/// would otherwise hash `obj/` (which MSBuild mutates on every build), so +/// task hashes would never stabilize and nothing would ever be a cache hit. +fn stable_inputs(inputs: &InferInputs) -> AnyResult> { + let mut list = vec![Input::parse("**/*").map_err(|error| anyhow!("{error}"))?]; + + for property in ["BaseOutputPath", "BaseIntermediateOutputPath"] { + if let Some(dir) = resolve_output_path( + inputs.evaluation.property(property), + inputs.project_dir, + inputs.workspace_dir, + ) { + list.push(Input::parse(format!("!{dir}/**")).map_err(|error| anyhow!("{error}"))?); + } + } + + Ok(list) +} + +/// Give a task its evaluated outputs, or disable caching when they could +/// not be determined (never cache the wrong directory). +fn apply_outputs(task: &mut PartialTaskConfig, outputs: Option) -> AnyResult<()> { + match outputs { + Some(path) => { + task.outputs = Some(vec![ + Output::parse(&path).map_err(|error| anyhow!("{error}"))?, + ]); + } + None => { + task.options.get_or_insert_default().cache = Some(TaskOptionCache::Enabled(false)); + } + } + + Ok(()) +} + +/// Task ids that inference would have contributed but had to yield to an +/// inherited task file, paired with the file that claimed each one. +/// +/// Yielding is silent otherwise, which turns "why does no project have a +/// build task?" into a dead end — worth one report per workspace. +pub fn reportable_conflicts<'a>( + reserved: &'a BTreeMap, + setting: &InferTasksSetting, +) -> Vec<(&'a str, &'a str)> { + INFERABLE_TASKS + .iter() + .filter(|task| setting.includes(task)) + .filter_map(|task| { + reserved + .get_key_value(*task) + .map(|(id, file)| (id.as_str(), file.as_str())) + }) + .collect() +} + +/// Infer `build` / `test` / `run` / `publish` tasks from one project's +/// MSBuild evaluation. +/// +/// - `build` — every project; `--no-dependencies` so moon's task graph +/// (`deps: ^:build`) orchestrates upstream builds and caches each project +/// independently (verified: MSBuild resolves `ProjectReference`s from the +/// upstream `bin` output without rebuilding them). +/// - `test` — projects with `IsTestProject=true` or a `Microsoft.NET.Test.Sdk` +/// reference; `--no-build` on top of a `build` dep. +/// - `run` — `Exe`/`WinExe` non-test projects; never cached, never in CI. +/// - `publish` — `Exe`/`WinExe` non-test single-TFM projects (multi-TFM +/// `dotnet publish` requires an explicit `-f`); `--no-build` on top of a +/// `build` dep. +/// +/// `restore` is deliberately NOT a task: moon models it as the +/// install-dependencies action (with `--locked-mode`), which runs before +/// tasks — hence `--no-restore` everywhere. +/// +/// `reserved_ids` (task ids from applicable inherited task files) are +/// skipped entirely: moon merges plugin tasks over inherited tasks with +/// args-append semantics, which produces garbage commands — yielding is the +/// only safe move. Project-level `moon.yml` tasks need no such handling; +/// moon itself guarantees they win over plugin tasks. +pub fn infer_tasks( + setting: &InferTasksSetting, + reserved_ids: &BTreeSet, + inputs: &InferInputs, +) -> AnyResult> { + let mut tasks = BTreeMap::new(); + let evaluation = inputs.evaluation; + + // Three independent signals, because no single one covers the ecosystem: + // + // - `IsTestProject` comes from Microsoft.NET.Test.Sdk's build props, so it + // is only set once that package is restored. + // - `IsTestingPlatformApplication` is set by test-oriented project SDKs + // (``) without needing a restore, and by + // Microsoft.Testing.Platform packages once restored. + // - The package references themselves are visible without any restore, + // which is the situation during a cold project-graph build. + // + // Verified against real repositories: an MSTest.Sdk project reports only + // `IsTestingPlatformApplication`, an xunit.v3 project on an unrestored tree + // reports only its package, and a BenchmarkDotNet project sets both + // properties to `false` and must stay excluded. + let is_test = evaluation + .property("IsTestProject") + .eq_ignore_ascii_case("true") + || evaluation + .property("IsTestingPlatformApplication") + .eq_ignore_ascii_case("true") + || evaluation + .package_references() + .keys() + .any(|name| is_test_package(name)); + + let output_type = evaluation.property("OutputType"); + let is_exe = !is_test + && (output_type.eq_ignore_ascii_case("Exe") || output_type.eq_ignore_ascii_case("WinExe")); + let is_single_tfm = evaluation.property("TargetFrameworks").is_empty(); + + let wants = |task: &str| setting.includes(task) && !reserved_ids.contains(task); + let file = inputs.explicit_project_file; + + let hash_inputs = stable_inputs(inputs)?; + let configuration = evaluation.property("Configuration"); + + if wants("build") { + let mut build_command = command("build", file, &["--no-restore", "--no-dependencies"]); + pin_configuration(&mut build_command, configuration); + + let mut task = PartialTaskConfig { + command: Some(build_command), + deps: Some(vec![parse_target("^:build")?]), + description: Some( + "Builds the project. Upstream projects build through moon task deps. (inferred)" + .into(), + ), + inputs: Some(hash_inputs.clone()), + ..Default::default() + }; + + apply_outputs( + &mut task, + resolve_output_path( + evaluation.property("BaseOutputPath"), + inputs.project_dir, + inputs.workspace_dir, + ), + )?; + + tasks.insert(Id::raw("build"), task); + } + + if is_test && wants("test") { + // Microsoft.Testing.Platform's `dotnet test` takes the project + // through `--project` and rejects a positional path; classic VSTest + // mode is the exact opposite and rejects `--project`. Both verified + // against SDK 10.0.201, so the flavour has to match the runner. + let uses_test_platform = inputs.test_platform_runner + || evaluation + .property("TestingPlatformDotnetTestSupport") + .eq_ignore_ascii_case("true"); + + let mut test_command = match file { + Some(file) if uses_test_platform => command( + "test", + None, + &["--project", file, "--no-build", "--no-restore"], + ), + _ => command("test", file, &["--no-build", "--no-restore"]), + }; + + pin_configuration(&mut test_command, configuration); + + tasks.insert( + Id::raw("test"), + PartialTaskConfig { + command: Some(test_command), + deps: Some(vec![parse_optional_target("~:build")?]), + description: Some("Runs tests against the built assemblies. (inferred)".into()), + inputs: Some(hash_inputs.clone()), + ..Default::default() + }, + ); + } + + if is_exe && wants("run") { + tasks.insert( + Id::raw("run"), + PartialTaskConfig { + command: Some(if let Some(file) = file { + command("run", None, &["--project", file]) + } else { + command("run", None, &[]) + }), + description: Some("Runs the application locally. (inferred)".into()), + options: Some(PartialTaskOptionsConfig { + cache: Some(TaskOptionCache::Enabled(false)), + run_in_ci: Some(TaskOptionRunInCI::Enabled(false)), + ..Default::default() + }), + ..Default::default() + }, + ); + } + + if is_exe && is_single_tfm && wants("publish") { + let mut publish_command = command("publish", file, &["--no-build", "--no-restore"]); + pin_configuration(&mut publish_command, configuration); + + let mut task = PartialTaskConfig { + command: Some(publish_command), + deps: Some(vec![parse_optional_target("~:build")?]), + description: Some("Publishes the built application. (inferred)".into()), + inputs: Some(hash_inputs), + ..Default::default() + }; + + apply_outputs( + &mut task, + resolve_output_path( + evaluation.property("PublishDir"), + inputs.project_dir, + inputs.workspace_dir, + ), + )?; + + tasks.insert(Id::raw("publish"), task); + } + + Ok(tasks) +} diff --git a/toolchains/dotnet/src/inherited_tasks.rs b/toolchains/dotnet/src/inherited_tasks.rs new file mode 100644 index 00000000..a7d2ba40 --- /dev/null +++ b/toolchains/dotnet/src/inherited_tasks.rs @@ -0,0 +1,173 @@ +//! Which task ids are already claimed by moon's inherited task files. +//! +//! Task inference must never contribute an id an inherited task file defines. +//! moon replaces a project-level task wholesale, but *merges* over an inherited +//! one with args appended — so an inferred `dotnet run` landing on an inherited +//! `echo inherited-run` produces `dotnet inherited-run run`. Yielding the id +//! entirely is the only safe option. + +use moon_pdk_api::VirtualPath; +use starbase_utils::{fs, yaml}; +use std::collections::BTreeMap; + +/// Partial shape of an inherited tasks file (`.moon/tasks.yml` or +/// `.moon/tasks/**/*.yml`) — just enough to know which task ids it defines +/// and whether it can apply to dotnet projects. +#[derive(Debug, Default, serde::Deserialize)] +#[serde(default, rename_all = "camelCase")] +struct InheritedTasksFile { + inherited_by: Option, + tasks: BTreeMap, +} + +#[derive(Debug, Default, serde::Deserialize)] +#[serde(default, rename_all = "camelCase")] +struct InheritedByScope { + toolchains: Option>, + languages: Option>, +} + +/// Can an inherited tasks file apply to dotnet projects? Only an explicit +/// `inheritedBy` scope naming other toolchains/languages rules it out; +/// everything else (unscoped, tag/stack/layer-scoped) is conservatively +/// assumed to apply — suppressing an inferred task is recoverable, while +/// moon's args-append merge of an inferred task over an inherited one +/// produces garbage commands. +fn applies_to_dotnet(scope: Option<&InheritedByScope>) -> bool { + let Some(scope) = scope else { + return true; + }; + + let mut scoped = false; + + if let Some(toolchains) = &scope.toolchains { + scoped = true; + + if toolchains + .iter() + .any(|id| id.eq_ignore_ascii_case("dotnet")) + { + return true; + } + } + + if let Some(languages) = &scope.languages { + scoped = true; + + if languages.iter().any(|lang| { + matches!( + lang.to_lowercase().as_str(), + "csharp" | "c#" | "fsharp" | "f#" | "vb" | "visualbasic" | "dotnet" + ) + }) { + return true; + } + } + + !scoped +} + +fn collect_yaml_files(dir: &VirtualPath, out: &mut Vec) { + if let Ok(entries) = fs::read_dir(dir.any_path()) { + for entry in entries { + let Ok(name) = entry.file_name().into_string() else { + continue; + }; + + let path = dir.join(&name); + + if entry.file_type().is_ok_and(|kind| kind.is_dir()) { + collect_yaml_files(&path, out); + } else if name.ends_with(".yml") || name.ends_with(".yaml") { + out.push(path); + } + } + } +} + +/// Task ids defined in inherited task files that can apply to dotnet +/// projects, mapped to the file that defines each one (for reporting). +/// Inference must never contribute one of these ids — see +/// `applies_to_dotnet` for why. +pub fn load_inherited_task_ids(workspace_root: &VirtualPath) -> BTreeMap { + let mut ids = BTreeMap::new(); + let mut files = vec![workspace_root.join(".moon").join("tasks.yml")]; + + collect_yaml_files(&workspace_root.join(".moon").join("tasks"), &mut files); + + for file in files { + if !file.exists() { + continue; + } + + // An unparseable file is moon's problem to report; there is nothing + // for inference to yield to. + if let Ok(parsed) = yaml::read_file::(file.any_path()) + && applies_to_dotnet(parsed.inherited_by.as_ref()) + { + let label = file.to_string(); + + for id in parsed.tasks.into_keys() { + ids.entry(id).or_insert_with(|| label.clone()); + } + } + } + + ids +} + +#[cfg(test)] +mod tests { + use super::*; + + fn scope(toolchains: Option<&[&str]>, languages: Option<&[&str]>) -> InheritedByScope { + InheritedByScope { + toolchains: toolchains.map(|list| list.iter().map(|id| id.to_string()).collect()), + languages: languages.map(|list| list.iter().map(|id| id.to_string()).collect()), + } + } + + #[test] + fn unscoped_files_always_apply() { + assert!(applies_to_dotnet(None)); + assert!(applies_to_dotnet(Some(&scope(None, None)))); + } + + #[test] + fn matching_toolchain_or_language_applies() { + assert!(applies_to_dotnet(Some(&scope(Some(&["dotnet"]), None)))); + assert!(applies_to_dotnet(Some(&scope(Some(&["DotNet"]), None)))); + assert!(applies_to_dotnet(Some(&scope(None, Some(&["csharp"]))))); + assert!(applies_to_dotnet(Some(&scope(None, Some(&["F#"]))))); + assert!(applies_to_dotnet(Some(&scope( + None, + Some(&["visualbasic"]) + )))); + } + + #[test] + fn a_scope_naming_only_other_toolchains_does_not_apply() { + assert!(!applies_to_dotnet(Some(&scope(Some(&["node"]), None)))); + assert!(!applies_to_dotnet(Some(&scope( + None, + Some(&["typescript"]) + )))); + assert!(!applies_to_dotnet(Some(&scope( + Some(&["rust"]), + Some(&["go"]) + )))); + } + + #[test] + fn a_match_in_either_dimension_is_enough() { + // Scoped to another toolchain but a .NET language, or vice versa. + assert!(applies_to_dotnet(Some(&scope( + Some(&["node"]), + Some(&["csharp"]) + )))); + assert!(applies_to_dotnet(Some(&scope( + Some(&["dotnet"]), + Some(&["typescript"]) + )))); + } +} diff --git a/toolchains/dotnet/src/lib.rs b/toolchains/dotnet/src/lib.rs new file mode 100644 index 00000000..d09da596 --- /dev/null +++ b/toolchains/dotnet/src/lib.rs @@ -0,0 +1,31 @@ +pub mod config; +pub mod discovery; +pub mod dotnet_install; +pub mod eval_cache; +pub mod global_json; +pub mod infer_tasks; +pub mod inherited_tasks; +pub mod msbuild; +pub mod nuget_lock; + +#[cfg(feature = "wasm")] +mod project_graph; +#[cfg(feature = "wasm")] +mod tier1; +#[cfg(feature = "wasm")] +mod tier2; +#[cfg(feature = "wasm")] +mod tier2_env; +#[cfg(feature = "wasm")] +mod tier3; + +#[cfg(feature = "wasm")] +pub use project_graph::*; +#[cfg(feature = "wasm")] +pub use tier1::*; +#[cfg(feature = "wasm")] +pub use tier2::*; +#[cfg(feature = "wasm")] +pub use tier2_env::*; +#[cfg(feature = "wasm")] +pub use tier3::*; diff --git a/toolchains/dotnet/src/msbuild.rs b/toolchains/dotnet/src/msbuild.rs new file mode 100644 index 00000000..36650447 --- /dev/null +++ b/toolchains/dotnet/src/msbuild.rs @@ -0,0 +1,564 @@ +use moon_pdk_api::AnyResult; +use serde::Deserialize; +use std::collections::BTreeMap; + +/// Result of an MSBuild evaluation via `dotnet msbuild -getProperty:... -getItem:...`. +#[derive(Clone, Debug, Default, Deserialize)] +pub struct MsbuildEvaluation { + #[serde(rename = "Properties", default)] + pub properties: BTreeMap, + + #[serde(rename = "Items", default)] + pub items: BTreeMap>, +} + +impl MsbuildEvaluation { + pub fn property(&self, name: &str) -> &str { + self.properties.get(name).map(String::as_str).unwrap_or("") + } + + /// `FullPath` of every ProjectReference item (host-real absolute paths). + pub fn project_reference_paths(&self) -> Vec { + self.items + .get("ProjectReference") + .map(|items| { + items + .iter() + .filter_map(|item| item.get("FullPath")) + .filter_map(|value| value.as_str()) + .map(|value| value.to_owned()) + .collect() + }) + .unwrap_or_default() + } + + /// PackageReference `Identity` -> `Version` (missing version becomes `*`). + pub fn package_references(&self) -> BTreeMap { + self.identity_version_items("PackageReference", "*") + } + + /// PackageVersion `Identity` -> `Version` (Central Package Management + /// declarations from `Directory.Packages.props`; empty without CPM). + pub fn package_versions(&self) -> BTreeMap { + self.identity_version_items("PackageVersion", "") + } + + fn identity_version_items( + &self, + item_type: &str, + missing_version: &str, + ) -> BTreeMap { + self.items + .get(item_type) + .map(|items| { + items + .iter() + .filter_map(|item| { + let identity = item.get("Identity")?.as_str()?; + let version = item + .get("Version") + .and_then(|value| value.as_str()) + .filter(|value| !value.is_empty()) + .unwrap_or(missing_version); + + Some((identity.to_owned(), version.to_owned())) + }) + .collect() + }) + .unwrap_or_default() + } +} + +/// The exact `-getProperty` list requested per evaluation. +/// `BaseOutputPath`/`BaseIntermediateOutputPath`/`PublishDir` feed inferred +/// task outputs and input exclusions (they follow redirected output +/// locations, e.g. .NET 8 `UseArtifactsOutput`). `AssemblyName`/`Version` +/// feed the project alias and manifest metadata. +/// `TestingPlatformDotnetTestSupport` opts a single project into +/// Microsoft.Testing.Platform, which changes the `dotnet test` command line. +/// `IsTestingPlatformApplication` identifies a test project that carries no +/// `Microsoft.NET.Test.Sdk` reference at all, which is the norm for the +/// test-oriented project SDKs (``). +pub const EVAL_PROPERTIES: &str = "TargetFramework,TargetFrameworks,OutputType,IsTestProject,IsTestingPlatformApplication,IsPackable,RestorePackagesWithLockFile,BaseOutputPath,BaseIntermediateOutputPath,PublishDir,Configuration,AssemblyName,Version,TestingPlatformDotnetTestSupport"; + +/// The exact `-getItem` list requested per evaluation. `PackageVersion` +/// items exist under Central Package Management (declared in +/// `Directory.Packages.props`) and are empty otherwise. +/// +/// Only `ProjectReference` and `PackageReference` come back from *batched* +/// evaluation — the injected target flattens those two into item metadata. +/// `PackageVersion` is therefore only ever populated on the per-project path, +/// which is where it is needed: `parse_manifest` evaluates a +/// `Directory.Packages.props` singly. +pub const EVAL_ITEMS: &str = "ProjectReference,PackageReference,PackageVersion"; + +/// Parse the stdout of an MSBuild `-get*` invocation. MSBuild may print stray +/// warnings before the JSON — start at the first `{`. +pub fn parse_msbuild_output(stdout: &str) -> AnyResult { + let json_start = stdout + .find('{') + .ok_or_else(|| moon_pdk_api::anyhow!("no JSON found in MSBuild output"))?; + + Ok(serde_json::from_str(&stdout[json_start..])?) +} + +/// Lexically normalize a host path for cross-referencing MSBuild output +/// against moon project paths: forward slashes, lowercased (paths on Windows +/// are case-insensitive; MSBuild output casing is not guaranteed to match +/// the on-disk casing moon reports). +pub fn normalize_path_key(path: &str) -> String { + path.replace('\\', "/").to_lowercase() +} + +/// Host environment applied to every MSBuild invocation, so graph evaluation +/// resolves the same SDK that tasks will run under. +#[derive(Clone, Debug, Default)] +pub struct EvalEnv { + /// `DOTNET_ROOT` to evaluate under, when one was resolved. + pub dotnet_root: Option, + + /// Absolute path to the `dotnet` muxer inside `dotnet_root`, when its + /// existence could be confirmed. + /// + /// This is what actually selects the SDK: the host resolves a bare + /// command name from its own `PATH` (warpgate `host.rs` — a command + /// containing a separator is treated as a path, anything else goes + /// through `find_command_on_path`), and the muxer locates SDKs relative + /// to its own location rather than from `DOTNET_ROOT`. Verified + /// empirically: setting only `DOTNET_ROOT`/`paths` left evaluation on the + /// `PATH` SDK. + pub dotnet_exe: Option, + + /// Directory to run MSBuild in. The dotnet host resolves `global.json` + /// from the **current directory** — not from the project path (verified + /// empirically) — so this is what decides which SDK evaluates the + /// projects. Leaving it unset would inherit moon's own working directory, + /// making evaluation depend on where the user happened to run moon from. + pub cwd: Option, +} + +/// Deepest directory that contains all of the given workspace-relative +/// project sources, as a workspace-relative path (empty when they share no +/// prefix, i.e. the workspace root itself). +/// +/// Used as the evaluation working directory: in a repo whose .NET projects +/// live under one subtree, this is the subtree root, so a `global.json` there +/// applies to evaluation exactly as it applies to the tasks that run inside +/// it. +pub fn common_source_prefix(sources: &[&str]) -> String { + let mut common: Option> = None; + + for source in sources { + let parts = source + .split(['/', '\\']) + .filter(|part| !part.is_empty() && *part != ".") + .collect::>(); + + common = Some(match common { + None => parts, + Some(existing) => existing + .into_iter() + .zip(parts) + // Sources come from moon config, which is case-sensitive + // about the paths it reports; compare them verbatim. + .take_while(|(left, right)| left == right) + .map(|(left, _)| left) + .collect(), + }); + + if common.as_ref().is_some_and(|parts| parts.is_empty()) { + break; + } + } + + common.unwrap_or_default().join("/") +} + +/// Escape a literal path for use inside an MSBuild `Include` attribute: +/// MSBuild's own special characters (property/item expansion, list +/// separators, globs) via `%XX` escapes, then XML attribute characters. +pub fn escape_msbuild_include(path: &str) -> String { + path.replace('%', "%25") + .replace('$', "%24") + .replace('@', "%40") + .replace(';', "%3B") + .replace('*', "%2A") + .replace('?', "%3F") + .replace('&', "&") + .replace('<', "<") + .replace('>', ">") + .replace('"', """) +} + +/// Item metadata name carrying the `|`-joined ProjectReference full paths in +/// batched evaluation output. +const BATCH_PROJECT_REFS: &str = "MoonProjectRefs"; + +/// Item metadata name carrying the `|`-joined `Identity@Version` +/// PackageReference entries in batched evaluation output. +const BATCH_PACKAGE_REFS: &str = "MoonPackageRefs"; + +/// The `.targets` file injected into every project during batched evaluation +/// (via the `CustomAfterMicrosoftCommon(CrossTargeting)Targets` hooks): a +/// target that returns the project's evaluation state as one item with +/// metadata. It runs as the entry target with no dependencies, so item state +/// when it executes is identical to evaluation state — the same answers as a +/// per-project `-getItem` query. +pub fn moon_eval_targets_xml() -> String { + let properties = EVAL_PROPERTIES + .split(',') + .map(|prop| format!(" <{prop}>$({prop})\n")) + .collect::(); + + format!( + r#" + + + <_MoonEvalResult Include="$(MSBuildProjectFullPath)"> +{properties} <{BATCH_PROJECT_REFS}>@(ProjectReference->'%(FullPath)', '|') + <{BATCH_PACKAGE_REFS}>@(PackageReference->'%(Identity)@%(Version)', '|') + + + + +"# + ) +} + +/// The traversal project for batched evaluation: fans out to every listed +/// project with `BuildInParallel` (in-process MSBuild worker nodes) and +/// collects the injected target's outputs. A raw `` with no `Sdk` +/// attribute imports nothing implicitly, so the workspace's own +/// `Directory.Build.props` cannot interfere with the traversal itself, while +/// the child projects still evaluate with their full normal import chains. +/// `ContinueOnError` keeps one broken project from aborting the batch — it +/// just goes missing from the output (and falls back to per-project +/// evaluation). +pub fn traversal_project_xml(project_paths: &[String]) -> String { + let includes = project_paths + .iter() + .map(|path| { + format!( + " \n", + escape_msbuild_include(path) + ) + }) + .collect::(); + + format!( + r#" + +{includes} + + + + + + +"# + ) +} + +/// Parse the `-getItem:MoonEval` JSON of a batched traversal invocation into +/// per-project evaluations. Each project is keyed (normalized) by every +/// identifying path on its item: the traversal `Include` we wrote +/// (`OriginalItemSpec`) and MSBuild's own expanded full path +/// (`MSBuildSourceProjectFile` / `Identity`) — these can differ lexically, +/// e.g. Windows 8.3 short names in temp directories. +pub fn parse_batch_output(stdout: &str) -> AnyResult> { + let raw = parse_msbuild_output(stdout)?; + let mut results = BTreeMap::new(); + + let Some(items) = raw.items.get("MoonEval") else { + return Ok(results); + }; + + for item in items { + let metadata = |name: &str| { + item.get(name) + .and_then(|value| value.as_str()) + .unwrap_or("") + }; + + let mut evaluation = MsbuildEvaluation::default(); + + for prop in EVAL_PROPERTIES.split(',') { + evaluation + .properties + .insert(prop.to_owned(), metadata(prop).to_owned()); + } + + let project_refs = metadata(BATCH_PROJECT_REFS) + .split('|') + .filter(|path| !path.is_empty()) + .map(|path| serde_json::json!({ "FullPath": path })) + .collect::>(); + + if !project_refs.is_empty() { + evaluation + .items + .insert("ProjectReference".to_owned(), project_refs); + } + + let package_refs = metadata(BATCH_PACKAGE_REFS) + .split('|') + .filter(|entry| !entry.is_empty()) + .map(|entry| { + // '@' cannot appear in NuGet package ids or versions; an + // empty version (e.g. Central Package Management) becomes + // `*` downstream. + let (identity, version) = entry.rsplit_once('@').unwrap_or((entry, "")); + serde_json::json!({ "Identity": identity, "Version": version }) + }) + .collect::>(); + + if !package_refs.is_empty() { + evaluation + .items + .insert("PackageReference".to_owned(), package_refs); + } + + for key_field in ["OriginalItemSpec", "MSBuildSourceProjectFile", "Identity"] { + let key = metadata(key_field); + + if !key.is_empty() { + results.insert(normalize_path_key(key), evaluation.clone()); + } + } + } + + Ok(results) +} + +/// Did an invocation fail because the dotnet host could not resolve an SDK +/// (rather than because a project is broken)? +/// +/// Matches on the help URL the host prints, which — unlike the surrounding +/// message text — is not localized. The English phrasing is accepted as a +/// fallback for hosts that omit the link. +pub fn is_sdk_resolution_failure(output: &str) -> bool { + let lower = output.to_lowercase(); + + lower.contains("aka.ms/dotnet/sdk-not-found") + || (lower.contains("global.json") && lower.contains("sdk") && lower.contains("not found")) +} + +/// Given the output of a failed batch invocation, find which of the input +/// projects MSBuild reported diagnostics for. +/// +/// MSBuild writes the file in two shapes, and both have to be recognized: +/// +/// ```text +/// (6,3): error MSB4025: The project file could not be loaded. ... +/// : error : Could not resolve SDK "Totally.Bogus.Sdk". ... +/// ``` +/// +/// The second form — no line/column, emitted for SDK resolution among others — +/// was missed, so a batch killed by an unresolvable SDK reference identified no +/// offender, the retry below never fired, and the whole batch was discarded. +/// +/// Only the trailing `/` suffix is matched, not the full path: +/// MSBuild prints expanded long paths, which can differ lexically from the ones +/// we passed (e.g. Windows 8.3 short names like `RUNNER~1` in a temp-dir +/// prefix). Both anchors keep the match at a token boundary so `App.csproj` +/// cannot match `MyApp.csproj`. +/// +/// Deliberately not filtered to lines containing `": error "`: MSBuild localizes +/// diagnostic text, so that would break on a non-English host. The cost is that +/// a path mentioned in a *warning* is treated as failed too — which merely +/// over-excludes, and an over-excluded project falls back to per-project +/// evaluation and stays correct. The same is true of a suffix shared by two +/// projects. +pub fn detect_failed_projects(output: &str, project_paths: &[String]) -> Vec { + let haystack = normalize_path_key(output); + + project_paths + .iter() + .filter(|path| { + let normalized = normalize_path_key(path); + + // From the second-to-last separator: "//" — the + // leading slash anchors the match to a component boundary. + let suffix = normalized + .rmatch_indices('/') + .nth(1) + .map(|(index, _)| &normalized[index..]) + .unwrap_or(&normalized); + + haystack.contains(&format!("{suffix}(")) || haystack.contains(&format!("{suffix} :")) + }) + .cloned() + .collect() +} + +/// Apply the resolved SDK environment to an MSBuild invocation. +#[cfg(feature = "wasm")] +fn with_eval_env( + mut input: moon_pdk_api::ExecCommandInput, + env: &EvalEnv, +) -> moon_pdk_api::ExecCommandInput { + if let Some(root) = &env.dotnet_root { + input.env.insert("DOTNET_ROOT".into(), root.clone()); + input + .paths + .push(moon_pdk_api::VirtualPath::Real(root.into())); + } + + // Only an explicit executable path redirects which SDK evaluates; see + // `EvalEnv::dotnet_exe`. + if let Some(exe) = &env.dotnet_exe { + input.command = exe.clone(); + } + + if let Some(cwd) = &env.cwd { + input.cwd = Some(cwd.clone()); + } + + input +} + +/// Evaluate many projects with a single MSBuild invocation, paying the +/// dotnet/MSBuild startup cost (which dominates per-project evaluation) +/// once instead of once per project, and evaluating in parallel. The +/// generated traversal files live under `.moon/cache/` in the workspace. +#[cfg(feature = "wasm")] +pub fn evaluate_projects_batch( + workspace_root: &moon_pdk_api::VirtualPath, + project_real_paths: &[std::path::PathBuf], + eval_env: &EvalEnv, +) -> AnyResult> { + use moon_pdk::exec; + use moon_pdk_api::{ExecCommandInput, anyhow}; + use starbase_utils::fs; + + // Known constraint: both scratch files use fixed names here, so two moon + // processes building a graph in the same checkout at the same time can have + // one truncating `traversal.proj` while the other's MSBuild reads it. A + // per-invocation subdirectory would fix it, but wasm has no pid, clock or + // randomness to name one with, and `MoonContext` offers only `working_dir` + // and `workspace_root` — a name derived from the project set would still + // collide for the identical batch. The failure is a malformed traversal + // project, which surfaces as a batch failure and falls back to per-project + // evaluation, so it degrades rather than corrupting results. + let dir = workspace_root + .join(".moon") + .join("cache") + .join("dotnet-toolchain"); + + fs::create_dir_all(&dir)?; + fs::write_file(dir.join("moon-eval.targets"), moon_eval_targets_xml())?; + + let traversal = dir.join("traversal.proj"); + + let traversal_arg = traversal + .real_path() + .ok_or_else(|| anyhow!("no host-real path for {traversal:?}"))? + .to_string_lossy() + .to_string(); + + let run = |batch_paths: &[String]| { + fs::write_file(&traversal, traversal_project_xml(batch_paths))?; + + exec(with_eval_env( + ExecCommandInput::pipe( + "dotnet", + [ + "msbuild", + traversal_arg.as_str(), + "-nologo", + // Parallel in-process worker nodes, but never leave them + // alive after the invocation (node reuse lingers ~15 min, + // which is hostile to CI containers). + "-maxCpuCount", + "-nodeReuse:false", + "-t:MoonCollect", + "-getItem:MoonEval", + ], + ), + eval_env, + )) + }; + + let paths = project_real_paths + .iter() + .map(|path| path.to_string_lossy().to_string()) + .collect::>(); + + let output = run(&paths)?; + + if output.exit_code == 0 { + return parse_batch_output(&output.stdout); + } + + // MSBuild returns NO target outputs at all when any project fails to + // load (ContinueOnError does not rescue load errors). Identify the + // offenders from the error lines and retry once without them — their + // absence from the result triggers the caller's per-project fallback, + // which surfaces the real error. + let combined = format!("{}{}", output.stdout, output.stderr); + let failed = detect_failed_projects(&combined, &paths); + + if !failed.is_empty() && failed.len() < paths.len() { + let remaining = paths + .iter() + .filter(|path| !failed.contains(path)) + .cloned() + .collect::>(); + + let retry = run(&remaining)?; + + if retry.exit_code == 0 { + return parse_batch_output(&retry.stdout); + } + } + + Err(anyhow!( + "Batched MSBuild evaluation failed (exit code {}): {}{}", + output.exit_code, + output.stdout, + output.stderr, + )) +} + +/// Run a real MSBuild evaluation for a project file (host-real path). +#[cfg(feature = "wasm")] +pub fn evaluate_project( + csproj_real_path: &std::path::Path, + eval_env: &EvalEnv, +) -> AnyResult { + use moon_pdk::exec; + use moon_pdk_api::{ExecCommandInput, anyhow}; + + let path_arg = csproj_real_path.to_string_lossy().to_string(); + + let output = exec(with_eval_env( + ExecCommandInput::pipe( + "dotnet", + [ + "msbuild", + path_arg.as_str(), + "-nologo", + &format!("-getProperty:{EVAL_PROPERTIES}"), + &format!("-getItem:{EVAL_ITEMS}"), + ], + ), + eval_env, + ))?; + + if output.exit_code != 0 { + return Err(anyhow!( + "MSBuild evaluation failed for {} (exit code {}): {}{}", + csproj_real_path.display(), + output.exit_code, + output.stdout, + output.stderr, + )); + } + + parse_msbuild_output(&output.stdout) +} diff --git a/toolchains/dotnet/src/nuget_lock.rs b/toolchains/dotnet/src/nuget_lock.rs new file mode 100644 index 00000000..a47a4329 --- /dev/null +++ b/toolchains/dotnet/src/nuget_lock.rs @@ -0,0 +1,104 @@ +use serde::Deserialize; +use std::collections::BTreeMap; + +/// A NuGet `packages.lock.json` file. +/// +/// Shape: `{ "version": 1, "dependencies": { "": { "": +/// { "type", "requested", "resolved", "contentHash", ... } } } }` +#[derive(Debug, Default, Deserialize)] +pub struct NugetLockFile { + #[serde(default)] + pub version: u32, + + #[serde(default)] + pub dependencies: BTreeMap>, +} + +#[derive(Debug, Default, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct NugetLockEntry { + /// "Direct", "Transitive", "Project", or "CentralTransitive". + #[serde(default, rename = "type")] + pub dep_type: String, + + #[serde(default)] + pub requested: Option, + + #[serde(default)] + pub resolved: Option, + + #[serde(default)] + pub content_hash: Option, +} + +pub fn parse_lock_file(content: &str) -> Result { + serde_json::from_str(content) +} + +#[cfg(test)] +mod tests { + use super::*; + + const SAMPLE: &str = r#"{ + "version": 1, + "dependencies": { + "net8.0": { + "Newtonsoft.Json": { + "type": "Direct", + "requested": "[13.0.3, )", + "resolved": "13.0.3", + "contentHash": "HrC5BXdl00IP9zeV+0Z848QWPAoCr9P3bDEZguI+gkLcBKAOxix/tLEAAHC+UvDNPv4a2d18lOReHMOagPa+zQ==" + }, + "App": { + "type": "Project", + "dependencies": { + "Lib": "[1.0.0, )" + } + } + }, + "net9.0": { + "Newtonsoft.Json": { + "type": "Direct", + "requested": "[13.0.3, )", + "resolved": "13.0.3", + "contentHash": "HrC5BXdl00IP9zeV+0Z848QWPAoCr9P3bDEZguI+gkLcBKAOxix/tLEAAHC+UvDNPv4a2d18lOReHMOagPa+zQ==" + } + } + } +}"#; + + #[test] + fn parses_lock_file() { + let lock = parse_lock_file(SAMPLE).unwrap(); + + assert_eq!(lock.version, 1); + assert_eq!(lock.dependencies.len(), 2); + + let net8 = &lock.dependencies["net8.0"]; + let newtonsoft = &net8["Newtonsoft.Json"]; + + assert_eq!(newtonsoft.dep_type, "Direct"); + assert_eq!(newtonsoft.requested.as_deref(), Some("[13.0.3, )")); + assert_eq!(newtonsoft.resolved.as_deref(), Some("13.0.3")); + assert!( + newtonsoft + .content_hash + .as_deref() + .unwrap() + .starts_with("HrC5") + ); + + // Project-type entries carry no resolved version/hash. + let project = &net8["App"]; + assert_eq!(project.dep_type, "Project"); + assert!(project.resolved.is_none()); + } + + #[test] + fn tolerates_empty_file_shape() { + let lock = parse_lock_file("{}").unwrap(); + + assert_eq!(lock.version, 0); + assert!(lock.dependencies.is_empty()); + } +} diff --git a/toolchains/dotnet/src/project_graph.rs b/toolchains/dotnet/src/project_graph.rs new file mode 100644 index 00000000..1a73d1b6 --- /dev/null +++ b/toolchains/dotnet/src/project_graph.rs @@ -0,0 +1,552 @@ +//! Contributing .NET structure to moon's project graph. +//! +//! `extend_project_graph` runs in three passes: index every project's MSBuild +//! files, evaluate them all in one batched MSBuild invocation, then map each +//! project's `ProjectReference` items onto moon project ids and infer its tasks. + +use crate::config::DotnetToolchainConfig; +use crate::discovery::{find_lock_files, find_project_files}; +use crate::eval_cache::write_eval_cache; +use crate::infer_tasks::{InferInputs, infer_tasks, reportable_conflicts}; +use crate::inherited_tasks::load_inherited_task_ids; +use crate::msbuild::{ + EvalEnv, MsbuildEvaluation, common_source_prefix, evaluate_project, evaluate_projects_batch, + is_sdk_resolution_failure, normalize_path_key, +}; +use crate::tier2_env::{ + build_eval_env, find_sdk_requirement, sdk_install_configured, uses_test_platform_runner, +}; +use extism_pdk::*; +use moon_config::DependencyScope; +use moon_pdk::{ + HostLogInput, HostLogTarget, command_exists, get_host_environment, host_log, + parse_toolchain_config, plugin_err, +}; +use moon_pdk_api::*; +use std::collections::{BTreeMap, BTreeSet}; + +#[host_fn] +extern "ExtismHost" { + fn host_log(input: Json); +} + +/// Everything the first pass discovers: which MSBuild files each moon project +/// owns, plus two indexes for resolving a `ProjectReference` path back to a moon +/// project id. +struct ProjectIndexes { + files: BTreeMap>, + + /// Normalized host-real project-file path -> owning project. + by_real_path: BTreeMap, + + /// Normalized workspace-relative `//` suffix -> owning + /// project, or `None` when two projects share the suffix (ambiguous). + /// + /// This exists because the exact real paths can differ lexically from what + /// MSBuild prints — Windows 8.3 short names such as `RUNNER~1` in a temp-dir + /// prefix expand to their long form in MSBuild output. + by_suffix: BTreeMap>, +} + +/// Pass 1: locate every project's MSBuild files and index them. +fn build_project_indexes(input: &ExtendProjectGraphInput) -> ProjectIndexes { + let mut indexes = ProjectIndexes { + files: BTreeMap::new(), + by_real_path: BTreeMap::new(), + by_suffix: BTreeMap::new(), + }; + + for (id, source) in &input.project_sources { + let project_root = input.context.workspace_root.join(source); + let files = find_project_files(&project_root); + + if files.is_empty() { + // Not a .NET project; none of our business. + continue; + } + + for file in &files { + if let Some(real) = file.real_path() { + indexes + .by_real_path + .insert(normalize_path_key(&real.to_string_lossy()), id.to_owned()); + } + + if let Some(name) = file.file_name().and_then(|name| name.to_str()) { + let source = source.trim_matches('/'); + let suffix = if source.is_empty() || source == "." { + normalize_path_key(&format!("/{name}")) + } else { + normalize_path_key(&format!("/{source}/{name}")) + }; + + indexes + .by_suffix + .entry(suffix) + .and_modify(|existing| *existing = None) + .or_insert_with(|| Some(id.to_owned())); + } + } + + indexes.files.insert(id.to_owned(), files); + } + + indexes +} + +/// Resolve a `ProjectReference` path to the moon project that owns it: exact +/// real-path match first, then the **longest** matching workspace-relative +/// suffix. +/// +/// Longest, not first. One key can end with several indexed suffixes: with +/// sources `lib` and `src/lib` both holding an `App.csproj`, a reference to +/// `/ws/src/lib/App.csproj` ends with both `/lib/app.csproj` and +/// `/src/lib/app.csproj`. Taking the first match in `BTreeMap` order returned +/// the lexicographically smaller `/lib/...`, i.e. a dependency edge pointing at +/// the wrong project — the worst failure mode here, since the graph is then +/// silently wrong rather than merely incomplete. +/// +/// There is no tie to break: two suffixes of equal length that are both +/// suffixes of the same key are the same string, and the index holds each key +/// once. Genuinely ambiguous suffixes are already recorded as `None` when the +/// index is built. +fn resolve_reference<'index>( + indexes: &'index ProjectIndexes, + reference: &str, +) -> Option<&'index Id> { + let key = normalize_path_key(reference); + + indexes.by_real_path.get(&key).or_else(|| { + indexes + .by_suffix + .iter() + .filter(|(suffix, id)| id.is_some() && key.ends_with(suffix.as_str())) + .max_by_key(|(suffix, _)| suffix.len()) + .and_then(|(_, id)| id.as_ref()) + }) +} + +/// The working directory to evaluate from: the deepest directory containing +/// every .NET project, so a `global.json` in that subtree governs evaluation +/// exactly as it governs the tasks that run inside it. Without an explicit +/// working directory the dotnet host would resolve `global.json` from wherever +/// moon happened to be invoked, so the same workspace could evaluate under +/// different SDKs run to run. +fn batch_eval_env( + config: &DotnetToolchainConfig, + input: &ExtendProjectGraphInput, + indexes: &ProjectIndexes, +) -> AnyResult { + let sources = input + .project_sources + .iter() + .filter(|(id, _)| indexes.files.contains_key(*id)) + .map(|(_, source)| source.as_str()) + .collect::>(); + + let eval_prefix = common_source_prefix(&sources); + let eval_dir = if eval_prefix.is_empty() { + input.context.workspace_root.clone() + } else { + input.context.workspace_root.join(&eval_prefix) + }; + + build_eval_env(config, eval_dir, &input.context.workspace_root) +} + +/// Pass 2: evaluate every project in a single batched MSBuild invocation (one +/// process, parallel in-process evaluation). The dotnet/MSBuild startup cost +/// dominates per-project evaluation, so this is the difference between minutes +/// and seconds on large workspaces. +/// +/// A recoverable batch failure yields an empty map: each project then falls back +/// to its own evaluation, keeping the batch purely an optimization. +/// +/// An unresolvable SDK is different — it dooms every project, so falling back +/// would only repeat the host's cryptic output once per project and still leave +/// the graph empty. It is reported once, naming the pin and the ways out, and +/// whether that report is fatal depends on whether anything is going to fix it: +/// +/// - No `version:` configured: nothing will install the missing SDK, so this is a +/// terminal misconfiguration and the graph build fails with the guidance. +/// - `version:` configured: tier 3 installs that SDK later in the same run — the +/// project graph is built before the action pipeline starts, so failing here +/// would deadlock the very bootstrap the setting exists for. Warns instead and +/// returns `None`, the same way a missing `dotnet` does. +/// +/// `None` means "contribute nothing at all", and is distinct from `Some(empty)`: +/// an empty batch sends every project through per-project evaluation, which is +/// right for a recoverable failure and wrong when no SDK exists — there it would +/// reproduce the host's output once per project, the exact noise this reports +/// once instead. +fn run_batch_evaluation( + input: &ExtendProjectGraphInput, + indexes: &ProjectIndexes, + eval_env: &EvalEnv, +) -> FnResult>> { + let all_project_paths = indexes + .files + .values() + .flatten() + .filter_map(|file| file.real_path()) + .collect::>(); + + match evaluate_projects_batch(&input.context.workspace_root, &all_project_paths, eval_env) { + Ok(results) => Ok(Some(results)), + Err(error) => { + let message = error.to_string(); + + if is_sdk_resolution_failure(&message) { + let pin = find_sdk_requirement( + eval_env + .cwd + .as_ref() + .unwrap_or(&input.context.workspace_root), + &input.context.workspace_root, + ); + + let requirement = match &pin { + Some((file, requirement)) => format!( + "The .NET SDK pinned by {} ({}) is not available", + file, requirement.version + ), + None => "No usable .NET SDK was found".to_owned(), + }; + + if sdk_install_configured(&input.context.workspace_root) { + host_log!( + warn, + "{requirement} yet, so .NET project graph evaluation is being skipped — no dependency edges or inferred tasks will be contributed on this run. moon installs the SDK configured by version later in this run; re-run afterwards to pick them up." + ); + + return Ok(None); + } + + return Err(plugin_err!( + "{requirement}, so MSBuild evaluation cannot run.\n\nInstall that SDK, set version under dotnet in .moon/toolchains.yml to have moon install it, or point dotnetRoot at an SDK that satisfies the pin.\n\n{message}" + )); + } + + host_log!( + warn, + "Batched MSBuild evaluation failed; falling back to per-project evaluation: {}", + error + ); + + Ok(Some(BTreeMap::new())) + } + } +} + +/// Task ids inference must not contribute, because an inherited task file +/// already defines them. moon merges plugin tasks over inherited ones with +/// args-append semantics, which produces a garbage command. Project-level +/// `moon.yml` needs no such handling: moon guarantees local tasks win. +fn reserved_task_ids( + config: &DotnetToolchainConfig, + workspace_root: &VirtualPath, +) -> AnyResult> { + let reserved = load_inherited_task_ids(workspace_root); + + // Report once per workspace, not once per project: without this, "no + // project has a build task" has no visible cause. + for (task_id, file) in reportable_conflicts(&reserved, &config.infer_tasks) { + host_log!( + warn, + "Not inferring the {} task: {} already defines it, and moon merges inherited and plugin tasks by appending args — which would produce a broken command. Rename or remove that task to let inference contribute, or list only the tasks you want in inferTasks.", + task_id, + file + ); + } + + Ok(reserved.into_keys().collect()) +} + +/// Shared, read-only state for mapping one project. +struct GraphContext<'a> { + config: &'a DotnetToolchainConfig, + indexes: &'a ProjectIndexes, + eval_env: &'a EvalEnv, + reserved_task_ids: &'a BTreeSet, + workspace_root: &'a VirtualPath, + + /// Host-real workspace root, for making evaluated output paths relative. + workspace_dir: &'a str, + + infer_tasks_enabled: bool, +} + +/// What one moon project contributed. +struct ProjectEvaluation { + output: ExtendProjectOutput, + packages: BTreeMap, + + /// Every one of the project's MSBuild files evaluated successfully. The + /// package set above may only be cached when this holds — a partial set is + /// indistinguishable from a complete one once written, and would then be + /// served under a digest that stays valid. + complete: bool, + + /// Project files to report to moon as graph inputs. + input_files: Vec, +} + +/// Pass 3, for one project: map its `ProjectReference` items onto moon project +/// ids, take its alias from the evaluated `AssemblyName`, and infer its tasks. +fn extend_one_project( + ctx: &GraphContext<'_>, + id: &Id, + files: &[VirtualPath], + batch: &mut BTreeMap, + test_platform_runner: bool, +) -> AnyResult { + let mut result = ProjectEvaluation { + output: ExtendProjectOutput::default(), + packages: BTreeMap::new(), + complete: true, + input_files: vec![], + }; + + let mut seen_deps: BTreeSet = BTreeSet::new(); + + for file in files { + let Some(real_path) = file.real_path() else { + result.complete = false; + continue; + }; + + let batch_key = normalize_path_key(&real_path.to_string_lossy()); + + let evaluation = if let Some(evaluation) = batch.remove(&batch_key) { + evaluation + } else { + // Fall back with the project's own directory as the working + // directory — the same `global.json` its tasks will resolve. + let single_env = EvalEnv { + cwd: file.parent().or_else(|| ctx.eval_env.cwd.clone()), + ..ctx.eval_env.clone() + }; + + match evaluate_project(&real_path, &single_env) { + Ok(evaluation) => evaluation, + Err(error) => { + // One broken project must not take down graph construction + // for the whole workspace. + host_log!( + warn, + "MSBuild evaluation failed for project {} ({}): {}", + id, + real_path.display(), + error + ); + + result.complete = false; + continue; + } + } + }; + + result.packages.extend(evaluation.package_references()); + + // Project alias from the evaluated AssemblyName, so tasks can reference + // the project by its .NET name (e.g. `moon run MyCompany.App:build`). + // moon silently skips aliases that collide with project ids or + // already-claimed aliases, and an alias equal to its own id is a no-op — + // no need to filter beyond emptiness here. + if result.output.alias.is_none() { + let assembly_name = evaluation.property("AssemblyName"); + + if !assembly_name.is_empty() { + result.output.alias = Some(assembly_name.to_owned()); + } + } + + if ctx.config.infer_dependencies { + for reference in evaluation.project_reference_paths() { + let Some(dep_id) = resolve_reference(ctx.indexes, &reference) else { + host_log!( + debug, + "Project {} references {} which is outside the moon workspace; skipping", + id, + reference + ); + continue; + }; + + if dep_id != id && seen_deps.insert(dep_id.to_owned()) { + let file_name = std::path::Path::new(&reference) + .file_name() + .map(|name| name.to_string_lossy().to_string()) + .unwrap_or(reference.clone()); + + result.output.dependencies.push(ProjectDependency { + id: dep_id.to_owned(), + scope: DependencyScope::Production, + via: Some(format!("project-reference {file_name}")), + }); + } + } + } + + if ctx.infer_tasks_enabled { + let project_dir = real_path + .parent() + .map(|dir| dir.to_string_lossy().to_string()) + .unwrap_or_default(); + + // Bare `dotnet build` errors on ambiguity when the directory holds + // several project files — pass the file explicitly. + let explicit_project_file = if files.len() > 1 { + file.file_name().and_then(|name| name.to_str()) + } else { + None + }; + + let inferred = infer_tasks( + &ctx.config.infer_tasks, + ctx.reserved_task_ids, + &InferInputs { + evaluation: &evaluation, + explicit_project_file, + project_dir: &project_dir, + workspace_dir: ctx.workspace_dir, + test_platform_runner, + }, + ); + + match inferred { + Ok(tasks) => { + for (task_id, task) in tasks { + result.output.tasks.entry(task_id).or_insert(task); + } + } + Err(error) => { + host_log!( + warn, + "Task inference failed for project {}: {}", + id, + error + ); + } + } + } + + if let Some(virtual_file) = file.virtual_path() { + result.input_files.push(virtual_file); + } + } + + Ok(result) +} + +#[plugin_fn] +pub fn extend_project_graph( + Json(mut input): Json, +) -> FnResult> { + // Taken rather than moved out, so `input` stays whole for the helpers below. + let config = parse_toolchain_config::(std::mem::take( + &mut input.toolchain_config, + ))?; + let mut output = ExtendProjectGraphOutput::default(); + + let infer_tasks_enabled = config.infer_tasks.any_enabled(); + + if !config.infer_dependencies && !infer_tasks_enabled { + return Ok(Json(output)); + } + + let indexes = build_project_indexes(&input); + + if indexes.files.is_empty() { + return Ok(Json(output)); + } + + // Degrade rather than fail, like `parse_manifest` and `hash_task_contents` + // below. The graph is built before the action pipeline runs, so a `version:` + // configured for tier 3 to install has not been installed yet on a fresh + // machine — erroring here would fail the whole-workspace graph, for every + // toolchain, before moon ever gets to install the SDK it was told to + // install. + if !command_exists(&get_host_environment()?, "dotnet") { + host_log!( + warn, + "No dotnet executable found on PATH, skipping .NET project graph evaluation — no dependency edges or inferred tasks will be contributed. Install a .NET 8+ SDK, or set version in .moon/toolchains.yml to have moon install one." + ); + + return Ok(Json(output)); + } + + let eval_env = batch_eval_env(&config, &input, &indexes)?; + + // `None` means no SDK is available to evaluate with. Returning here rather + // than continuing with an empty batch is what keeps a single unresolvable pin + // from being reported once per project by the per-project fallback. + let Some(mut batch) = run_batch_evaluation(&input, &indexes, &eval_env)? else { + return Ok(Json(output)); + }; + + let workspace_dir = input + .context + .workspace_root + .real_path() + .map(|path| path.to_string_lossy().to_string()) + .unwrap_or_default(); + + let reserved = if infer_tasks_enabled { + reserved_task_ids(&config, &input.context.workspace_root)? + } else { + BTreeSet::new() + }; + + let ctx = GraphContext { + config: &config, + indexes: &indexes, + eval_env: &eval_env, + reserved_task_ids: &reserved, + workspace_root: &input.context.workspace_root, + workspace_dir: &workspace_dir, + infer_tasks_enabled, + }; + + for (id, files) in &indexes.files { + let project_root = input + .project_sources + .get(id) + .map(|source| input.context.workspace_root.join(source)); + + // Which `dotnet test` flavour this project's tasks will run under. + let test_platform_runner = infer_tasks_enabled + && project_root + .as_ref() + .is_some_and(|root| uses_test_platform_runner(root, ctx.workspace_root)); + + let result = extend_one_project(&ctx, id, files, &mut batch, test_platform_runner)?; + + output.input_files.extend(result.input_files); + + // Hand the evaluated package set to task hashing. Projects with a lock + // file take the lock-file branch there and never need it. + if result.complete + && let Some(project_root) = project_root + && find_lock_files(&project_root).is_empty() + { + write_eval_cache( + &input.context.workspace_root, + id.as_str(), + &project_root, + result.packages, + ); + } + + if !result.output.dependencies.is_empty() + || !result.output.tasks.is_empty() + || result.output.alias.is_some() + { + output + .extended_projects + .insert(id.to_owned(), result.output); + } + } + + Ok(Json(output)) +} diff --git a/toolchains/dotnet/src/tier1.rs b/toolchains/dotnet/src/tier1.rs new file mode 100644 index 00000000..3af3908d --- /dev/null +++ b/toolchains/dotnet/src/tier1.rs @@ -0,0 +1,119 @@ +use crate::config::DotnetToolchainConfig; +use extism_pdk::*; +use moon_config::LanguageType; +use moon_pdk_api::*; +use schematic::SchemaBuilder; +use starbase_utils::fs; +use toolchain_common::enable_tracing; + +#[plugin_fn] +pub fn register_toolchain( + Json(_): Json, +) -> FnResult> { + enable_tracing(); + + Ok(Json(RegisterToolchainOutput { + name: ".NET".into(), + description: Some( + "Provides .NET SDK project-graph extraction, dependency install (dotnet restore), and Docker support for SDK-style C#/F#/VB projects.".into(), + ), + plugin_version: env!("CARGO_PKG_VERSION").into(), + language: Some(LanguageType::CSharp), + exe_names: vec!["dotnet".into()], + config_file_globs: vec![ + "*.{csproj,fsproj,vbproj}".into(), + "*.{sln,slnx}".into(), + "global.json".into(), + "Directory.Build.props".into(), + "Directory.Build.targets".into(), + "Directory.Build.rsp".into(), + "Directory.Packages.props".into(), + // NuGet accepts any casing; cover the ones seen in the wild so + // case-sensitive filesystems still match. + "{nuget,NuGet}.{config,Config}".into(), + // `packages..lock.json` via NuGetLockFilePath; the + // default name lives in lock_file_names (exact match only there). + "packages.*.lock.json".into(), + ], + // Project files (*.csproj) have variable names, which moon's + // literal-name manifest matching cannot express; their detection is + // covered by config_file_globs instead. Directory.Packages.props is + // the one fixed-name .NET manifest: registering it makes CPM version + // bumps re-trigger dependency installs via parse_manifest. + manifest_file_names: vec!["Directory.Packages.props".into()], + lock_file_names: vec!["packages.lock.json".into()], + // NuGet uses a global package cache, not an in-repo vendor dir. + vendor_dir_name: None, + })) +} + +#[plugin_fn] +pub fn define_toolchain_config() -> FnResult> { + Ok(Json(DefineToolchainConfigOutput { + schema: SchemaBuilder::build_root::(), + })) +} + +#[plugin_fn] +pub fn initialize_toolchain( + Json(_): Json, +) -> FnResult> { + // There is nothing to prompt for: every setting has a working default, and + // the SDK version is read from `global.json` rather than configured here. + Ok(Json(InitializeToolchainOutput::default())) +} + +#[plugin_fn] +pub fn define_docker_metadata( + Json(_): Json, +) -> FnResult> { + Ok(Json(DefineDockerMetadataOutput { + // Intentionally not derived from the configured `version`: doing so + // would mean reading toolchain config here to pick an image tag, which + // is a product decision rather than a default. `version` is honoured + // where it matters — tier 3 installs exactly that SDK. + default_image: Some("mcr.microsoft.com/dotnet/sdk:latest".into()), + scaffold_globs: vec![ + "**/*.{csproj,fsproj,vbproj}".into(), + "**/*.{sln,slnx}".into(), + "**/*.props".into(), + "**/*.targets".into(), + "**/Directory.Build.rsp".into(), + "**/{nuget,NuGet}.{config,Config}".into(), + "**/packages.lock.json".into(), + "**/packages.*.lock.json".into(), + "global.json".into(), + // bin/obj contain generated *.props (obj/*.nuget.g.props) and + // must never end up in the restore layer. + "!**/bin/**".into(), + "!**/obj/**".into(), + ], + })) +} + +#[plugin_fn] +pub fn prune_docker(Json(input): Json) -> FnResult> { + let mut output = PruneDockerOutput::default(); + + let mut roots = vec![input.root.clone()]; + + for project in &input.projects { + roots.push(input.context.get_project_root(project)); + } + + for root in roots { + for dir_name in ["bin", "obj"] { + let dir = root.join(dir_name); + + if dir.exists() { + fs::remove_dir_all(&dir)?; + + if let Some(file) = dir.virtual_path() { + output.changed_files.push(file); + } + } + } + } + + Ok(Json(output)) +} diff --git a/toolchains/dotnet/src/tier2.rs b/toolchains/dotnet/src/tier2.rs new file mode 100644 index 00000000..595ed583 --- /dev/null +++ b/toolchains/dotnet/src/tier2.rs @@ -0,0 +1,365 @@ +use crate::config::DotnetToolchainConfig; +use crate::discovery::{ + LOCKFILE_SEARCH_DEPTH, contains_lockfile, find_config_files, find_lock_files, + find_project_files, has_solution_file, walk_up, +}; +use crate::eval_cache::{read_eval_cache, write_eval_cache}; +use crate::msbuild::evaluate_project; +use crate::nuget_lock::parse_lock_file; +use crate::tier2_env::build_eval_env; +use extism_pdk::*; +use moon_config::{UnresolvedVersionSpec, VersionSpec}; +use moon_pdk::{ + HostLogInput, HostLogTarget, command_exists, get_host_environment, host_log, + is_project_toolchain_enabled, parse_toolchain_config, +}; +use moon_pdk_api::*; +use starbase_utils::fs; +use std::collections::BTreeMap; + +#[host_fn] +extern "ExtismHost" { + fn host_log(input: Json); +} + +#[plugin_fn] +pub fn locate_dependencies_root( + Json(input): Json, +) -> FnResult> { + let mut output = LocateDependenciesRootOutput::default(); + let workspace_root = &input.context.workspace_root; + + // Nearest solution file wins. + for dir in walk_up(&input.starting_dir, workspace_root) { + if has_solution_file(&dir) { + output.root = dir.virtual_path(); + break; + } + } + + // Fall back to the nearest lockfile, then the nearest project file. + for probe in [find_lock_files, find_project_files] { + if output.root.is_some() { + break; + } + + for dir in walk_up(&input.starting_dir, workspace_root) { + if !probe(&dir).is_empty() { + output.root = dir.virtual_path(); + break; + } + } + } + + // Single dependencies root for v1; no member globs. + output.members = None; + + Ok(Json(output)) +} + +#[plugin_fn] +pub fn install_dependencies( + Json(input): Json, +) -> FnResult> { + let config = parse_toolchain_config::(input.toolchain_config)?; + let mut output = InstallDependenciesOutput::default(); + + let mut args: Vec = vec!["restore".into()]; + + // The mere presence of a lock file opts a project into lock-file restore; + // --locked-mode additionally fails restore (NU1004) when declared + // dependencies drifted from the lock file. + if contains_lockfile(&input.root, LOCKFILE_SEARCH_DEPTH) { + args.push("--locked-mode".into()); + } + + args.extend(config.restore_args.iter().cloned()); + + output.install_command = Some( + ExecCommandInput::new("dotnet", args) + .cwd(input.root.clone()) + .into(), + ); + // NuGet has no dedupe concept. + output.dedupe_command = None; + + Ok(Json(output)) +} + +#[plugin_fn] +pub fn parse_lock(Json(input): Json) -> FnResult> { + let mut output = ParseLockOutput::default(); + let lock = parse_lock_file(&fs::read_file(&input.path)?)?; + + // Dedupe identical entries across target frameworks. + for entries in lock.dependencies.into_values() { + for (name, entry) in entries { + // Project-type entries are in-repo ProjectReferences, not packages. + if entry.dep_type.eq_ignore_ascii_case("Project") { + continue; + } + + let versions = output.dependencies.entry(name).or_default(); + + let version = entry + .resolved + .as_deref() + .and_then(|value| VersionSpec::parse(value).ok()); + + let already_present = versions.iter().any(|existing: &LockDependency| { + existing.version == version && existing.hash == entry.content_hash + }); + + if !already_present { + versions.push(LockDependency { + hash: entry.content_hash, + meta: None, + // NuGet ranges like "[13.0.3, )" may not parse; omit then. + req: entry + .requested + .as_deref() + .and_then(|value| UnresolvedVersionSpec::parse(value).ok()), + version, + }); + } + } + } + + Ok(Json(output)) +} + +#[plugin_fn] +pub fn parse_manifest( + Json(input): Json, +) -> FnResult> { + let mut output = ParseManifestOutput::default(); + + let Some(real_path) = input.path.real_path() else { + return Ok(Json(output)); + }; + + let env = get_host_environment()?; + + // Degrade silently like hash_task_contents: a missing dotnet must not + // fail moon's install fingerprinting. + if !command_exists(&env, "dotnet") { + return Ok(Json(output)); + } + + let manifest_dir = input + .path + .parent() + .unwrap_or_else(|| input.context.workspace_root.clone()); + + // `parse_manifest` carries no toolchain config, so an explicit + // `dotnetRoot` cannot be honored here; the env var and the guarded + // `~/.dotnet` fallback still apply. + let eval_env = build_eval_env( + &DotnetToolchainConfig::default(), + manifest_dir, + &input.context.workspace_root, + )?; + + let evaluation = match evaluate_project(&real_path, &eval_env) { + Ok(evaluation) => evaluation, + Err(error) => { + host_log!( + warn, + "MSBuild evaluation failed while parsing manifest {}: {}", + real_path.display(), + error + ); + + return Ok(Json(output)); + } + }; + + // NuGet range syntax ("[13.0.3]", "(1.0,2.0)") is not a moon version + // spec; keep the raw string as a reference so the dependency is still + // listed (it just won't contribute a version to fingerprints). + let to_dependency = |version: String| match UnresolvedVersionSpec::parse(&version) { + Ok(spec) => ManifestDependency::new(spec), + Err(_) => ManifestDependency::Config(ManifestDependencyConfig { + reference: Some(version), + ..Default::default() + }), + }; + + let is_packages_props = input + .path + .file_name() + .and_then(|name| name.to_str()) + .is_some_and(|name| name.eq_ignore_ascii_case("Directory.Packages.props")); + + if is_packages_props { + // Central Package Management: PackageVersion items declare the + // workspace-level versions that versionless PackageReferences + // inherit. This is the only manifest name moon can actually track + // for .NET — project files have variable names, which moon's + // literal-name manifest matching cannot express. + for (name, version) in evaluation.package_versions() { + output.dependencies.insert(name, to_dependency(version)); + } + } else { + for (name, version) in evaluation.package_references() { + let dep = if version == "*" { + // Versionless under CPM: inherited from the workspace + // manifest (Directory.Packages.props). + ManifestDependency::inherited() + } else { + to_dependency(version) + }; + + output.dependencies.insert(name, dep); + } + + output.publishable = evaluation + .property("IsPackable") + .eq_ignore_ascii_case("true"); + } + + Ok(Json(output)) +} + +#[plugin_fn] +pub fn hash_task_contents( + Json(input): Json, +) -> FnResult> { + let mut output = HashTaskContentsOutput::default(); + + if !is_project_toolchain_enabled(&input.project) { + return Ok(Json(output)); + } + + let project_root = input.context.get_project_root(&input.project); + + // Config files (Directory.Build.props/targets/rsp, Directory.Packages.props, + // nuget.config, global.json) from the project dir up to the workspace root + // are always hashed: conditions/imports can make any of them affect the + // resolved package set, and props/targets/rsp change build behavior even + // when the package set is fully pinned by a lock file. Effects of custom + // ``s outside these conventions are only captured via the + // evaluated package set below, not content-hashed. + let mut configs: BTreeMap = BTreeMap::new(); + let workspace_root = &input.context.workspace_root; + + for dir in walk_up(&project_root, workspace_root) { + for file in find_config_files(&dir) { + let key = file + .virtual_path() + .map(|path| path.to_string_lossy().to_string()) + .unwrap_or_else(|| file.to_string()); + + configs.insert(key, fs::read_file(&file)?); + } + } + + // Lock file(s) present: their content already pins the entire resolved + // package set (incl. contentHashes) — include them raw and skip the + // costly MSBuild evaluation. + let lock_files = find_lock_files(&project_root); + + if !lock_files.is_empty() { + let mut lockfiles: BTreeMap = BTreeMap::new(); + + for file in &lock_files { + let key = file + .virtual_path() + .map(|path| path.to_string_lossy().to_string()) + .unwrap_or_else(|| file.to_string()); + + lockfiles.insert(key, fs::read_file(file)?); + } + + output.contents.push(json::json!({ + "configs": configs, + "lockfiles": lockfiles, + })); + + return Ok(Json(output)); + } + + // No lock file: hash the *evaluated* PackageReference set instead. + // + // Three levels of reuse, because this function runs once per task and a + // cold MSBuild evaluation costs ~0.5s per project: + // 1. a plugin-instance var, for repeated tasks of the same project; + // 2. the on-disk cache the batched graph evaluation primed, which is + // what keeps a lock-file-less workspace from paying one evaluation + // per project here (the batch already evaluated them all at once); + // 3. evaluating this project alone. + let cache_key = format!("eval-packages:{}", input.project.id); + + let packages: BTreeMap = if let Some(cached) = var::get::(&cache_key)? { + serde_json::from_str(&cached)? + } else if let Some(cached) = + read_eval_cache(workspace_root, input.project.id.as_str(), &project_root) + { + var::set(&cache_key, serde_json::to_string(&cached)?)?; + + cached + } else { + let mut packages = BTreeMap::new(); + let mut evaluated_all = false; + let env = get_host_environment()?; + + if command_exists(&env, "dotnet") { + let config = parse_toolchain_config::(input.toolchain_config)?; + let eval_env = build_eval_env(&config, project_root.clone(), workspace_root)?; + + evaluated_all = true; + + for file in find_project_files(&project_root) { + let Some(real_path) = file.real_path() else { + evaluated_all = false; + continue; + }; + + match evaluate_project(&real_path, &eval_env) { + Ok(evaluation) => { + packages.extend(evaluation.package_references()); + } + Err(error) => { + host_log!( + warn, + "MSBuild evaluation failed while hashing {}: {}", + input.project.id, + error + ); + + evaluated_all = false; + } + } + } + } + + // Kept regardless: the var is scoped to this plugin instance, so it + // stops us re-evaluating once per task while an SDK is genuinely + // missing, and it disappears with the process. + var::set(&cache_key, serde_json::to_string(&packages)?)?; + + // The on-disk cache only ever holds a complete set. Writing a partial + // one would persist it under a digest that keeps validating, and since + // this set is the only hash signal for a workspace without lock files, + // package changes would stop invalidating task hashes — moon would + // serve stale builds, and installing the missing SDK later would not + // recover it. + if evaluated_all { + write_eval_cache( + workspace_root, + input.project.id.as_str(), + &project_root, + packages.clone(), + ); + } + + packages + }; + + output.contents.push(json::json!({ + "configs": configs, + "packages": packages, + })); + + Ok(Json(output)) +} diff --git a/toolchains/dotnet/src/tier2_env.rs b/toolchains/dotnet/src/tier2_env.rs new file mode 100644 index 00000000..aa62eb21 --- /dev/null +++ b/toolchains/dotnet/src/tier2_env.rs @@ -0,0 +1,310 @@ +//! Resolving which SDK to use, and the environment tasks run under. +//! +//! Task environments and MSBuild evaluation must agree on a `DOTNET_ROOT`, or +//! the project graph gets evaluated by one SDK while tasks run under another. +//! Everything that decides that lives here, alongside the two tier-2 functions +//! that materialize an environment. + +use crate::config::DotnetToolchainConfig; +use crate::discovery::{installed_sdk_versions, walk_up}; +use crate::eval_cache::content_digest; +use crate::global_json::{SdkRequirement, parse_sdk_requirement, satisfies, selects_test_platform}; +use crate::msbuild::EvalEnv; +use extism_pdk::*; +use moon_pdk::{ + HostLogInput, HostLogTarget, command_exists, get_host_env_var, get_host_environment, host_log, + into_virtual_path, parse_toolchain_config, +}; +use moon_pdk_api::*; +use starbase_utils::{fs, yaml}; + +#[host_fn] +extern "ExtismHost" { + fn host_log(input: Json); +} + +/// Has moon been told to install a .NET SDK itself, via `version:` under +/// `dotnet` in `.moon/toolchains.yml`? +/// +/// That is a moon-level toolchain setting, not one of ours, so it never reaches +/// the plugin through `toolchain_config` — `setup_toolchain` receives it as +/// `configured_version`, but the project graph is built before any of that runs. +/// Reading the file is the only way to know at graph-build time, and it decides +/// whether an unresolvable SDK is a terminal misconfiguration or simply an SDK +/// that has not been installed yet. +pub fn sdk_install_configured(workspace_root: &VirtualPath) -> bool { + let file = workspace_root.join(".moon").join("toolchains.yml"); + + if !file.exists() { + return false; + } + + // Untyped: `version` may be a string (`'8.0'`), a bare YAML float (`8.0`) or + // an alias (`lts`), and only its presence matters here. + yaml::read_file::(file.any_path()) + .ok() + .and_then(|root| { + root.get("dotnet") + .and_then(|section| section.get("version")) + .cloned() + }) + .is_some_and(|version| !version.is_null()) +} + +/// Nearest `global.json` SDK pin, searching from `start` up to (and +/// including) the workspace root — the same direction the dotnet host +/// searches from its working directory. Returns the file path (for messages) +/// and the parsed pin. +/// +/// The search stops at the first `global.json` that exists, whether or not it +/// declares an `sdk.version`, because that is the one file the dotnet host +/// resolves — it neither merges them nor keeps looking. Walking past a pinless +/// file would attribute an ancestor's pin to a directory it does not govern, and +/// name that non-governing file in the diagnostics. `uses_test_platform_runner` +/// below already implements this rule; the two must agree. +pub fn find_sdk_requirement( + start: &VirtualPath, + workspace_root: &VirtualPath, +) -> Option<(String, SdkRequirement)> { + for dir in walk_up(start, workspace_root) { + let file = dir.join("global.json"); + + if file.exists() { + return fs::read_file(&file) + .ok() + .and_then(|content| parse_sdk_requirement(&content)) + .map(|requirement| (file.to_string(), requirement)); + } + } + + None +} + +/// Does the `global.json` governing this directory select +/// Microsoft.Testing.Platform for `dotnet test`? The nearest file wins, +/// whether or not it names a runner — the dotnet host resolves exactly one +/// `global.json`, it does not merge them. +pub fn uses_test_platform_runner(start: &VirtualPath, workspace_root: &VirtualPath) -> bool { + for dir in walk_up(start, workspace_root) { + let file = dir.join("global.json"); + + if file.exists() { + return fs::read_file(&file) + .map(|content| selects_test_platform(&content)) + .unwrap_or(false); + } + } + + false +} + +/// Where to look for a `global.json` SDK pin when validating the `~/.dotnet` +/// fallback: from `start` up to (and including) `workspace_root`. +pub struct SdkPinScope<'a> { + pub start: &'a VirtualPath, + pub workspace_root: &'a VirtualPath, +} + +/// Resolve the DOTNET_ROOT for task environments *and* MSBuild evaluation — +/// both must agree, or the graph gets evaluated by one SDK while tasks run +/// under another. +/// +/// Order: explicit config > existing host env var > `~/.dotnet` when it holds +/// a real SDK layout (where the proto dotnet plugin installs). +/// +/// The `~/.dotnet` fallback is guarded: a leftover install there (a stale +/// proto experiment, say) would otherwise be injected over a perfectly good +/// system SDK, making every task fail against a `global.json` pin it cannot +/// satisfy. When a `dotnet` exists on PATH and the fallback cannot serve the +/// workspace's pin, the fallback is skipped so PATH wins. Explicit +/// configuration is never second-guessed. +fn resolve_dotnet_root( + config: &DotnetToolchainConfig, + scope: Option>, +) -> AnyResult> { + if let Some(root) = &config.dotnet_root { + return Ok(Some(root.clone())); + } + + if let Some(existing) = get_host_env_var("DOTNET_ROOT")? + && !existing.is_empty() + { + return Ok(Some(existing)); + } + + let env = get_host_environment()?; + let candidate = env.home_dir.join(".dotnet"); + + // `~/.dotnet` doubles as the dotnet CLI's user-level cache directory, so + // mere existence is not enough — require the `dotnet` host executable, + // which a real SDK install provides. + let exe = if env.os.is_windows() { + "dotnet.exe" + } else { + "dotnet" + }; + + if !candidate.join(exe).exists() { + return Ok(None); + } + + if let Some(scope) = scope + && command_exists(&env, "dotnet") + && let Some((file, requirement)) = find_sdk_requirement(scope.start, scope.workspace_root) + { + let installed = installed_sdk_versions(&candidate); + + if !satisfies(&installed, &requirement) { + host_log!( + warn, + "Ignoring the ~/.dotnet fallback for DOTNET_ROOT: it has no SDK satisfying {} from {} (found: {}). Using the dotnet on PATH instead — set dotnetRoot to override.", + requirement.version, + file, + if installed.is_empty() { + "none".to_owned() + } else { + installed.join(", ") + } + ); + + return Ok(None); + } + } + + if let Some(real) = candidate.real_path() { + let root = real.to_string_lossy().to_string(); + + host_log!( + debug, + "Using the ~/.dotnet fallback as DOTNET_ROOT: {}", + root + ); + + return Ok(Some(root)); + } + + Ok(None) +} + +/// Build the MSBuild evaluation environment: the same DOTNET_ROOT tasks get, +/// plus an explicit working directory (`global.json` resolves from there). +pub fn build_eval_env( + config: &DotnetToolchainConfig, + cwd: VirtualPath, + workspace_root: &VirtualPath, +) -> AnyResult { + let dotnet_root = resolve_dotnet_root( + config, + Some(SdkPinScope { + start: &cwd, + workspace_root, + }), + )?; + + // Point at the muxer inside the root, but only when its existence can be + // confirmed — a host path must be virtualized before wasm can stat it, + // and roots outside the plugin's readable paths cannot be checked at all. + // Guessing would turn a working evaluation into "command not found", so + // unverifiable roots keep using the `dotnet` on PATH, as before. + let dotnet_exe = dotnet_root.as_ref().and_then(|root| { + let env = get_host_environment().ok()?; + let exe = if env.os.is_windows() { + "dotnet.exe" + } else { + "dotnet" + }; + let real = std::path::PathBuf::from(root).join(exe); + + into_virtual_path(&real) + .ok()? + .exists() + // The host converts a command containing a separator back from + // its virtual form, so pass the real path. + .then(|| real.to_string_lossy().to_string()) + }); + + Ok(EvalEnv { + dotnet_root, + dotnet_exe, + cwd: Some(cwd), + }) +} + +#[plugin_fn] +pub fn extend_task_command( + Json(input): Json, +) -> FnResult> { + let config = parse_toolchain_config::(input.toolchain_config)?; + let mut output = ExtendTaskCommandOutput::default(); + + // Tasks run in their project directory, so that is where the dotnet host + // resolves `global.json` from — validate the fallback against that pin. + let project_root = input.context.get_project_root(&input.project); + let scope = SdkPinScope { + start: &project_root, + workspace_root: &input.context.workspace_root, + }; + + // Deliberately only DOTNET_ROOT and PATH. Injecting vendor environment + // variables nobody asked for is surprising, and the + // `DOTNET_CLI_TELEMETRY_OPTOUT` that used to be set here was set *inside* + // this branch — so it never applied in the common case of a system SDK on + // PATH with no DOTNET_ROOT. It also does not suppress the "Welcome to .NET" + // first-run banner, which is what `DOTNET_NOLOGO` controls. Both belong in a + // task's own `env`, where they are visible. + if let Some(root) = resolve_dotnet_root(&config, Some(scope))? { + output.env.insert("DOTNET_ROOT".into(), root.clone()); + output.paths.push(root.into()); + } + + Ok(Json(output)) +} + +#[plugin_fn] +pub fn setup_environment( + Json(input): Json, +) -> FnResult> { + let mut output = SetupEnvironmentOutput::default(); + + // Restore local dotnet tools once per dependencies root when a tool + // manifest exists. Local tools (.config/dotnet-tools.json) are distinct + // from global tools, which remain out of scope. + // + // Search from the dependencies root up to the workspace root, the same + // way the dotnet CLI resolves a tool manifest: it conventionally lives at + // the repository root, which is not necessarily a dependencies root (any + // project directory holding a lock file becomes one). + let mut tool_manifest = None; + + for dir in walk_up(&input.root, &input.context.workspace_root) { + let candidate = dir.join(".config").join("dotnet-tools.json"); + + if candidate.exists() { + tool_manifest = Some(candidate); + break; + } + } + + if let Some(tool_manifest) = tool_manifest { + let mut command = ExecCommand::new( + ExecCommandInput::new("dotnet", ["tool", "restore"]).cwd(input.root.clone()), + ); + + command.label = Some("dotnet tool restore".into()); + + // The cache key carries a digest of the manifest content, because + // moon fingerprints this action on the *declaration* we return here + // and skips it wholesale when unchanged — a stable key would mean a + // manifest edit never re-runs the restore. `inputs` then prevents + // re-execution when the action runs again for unrelated reasons. + command.cache = Some(format!( + "dotnet-tool-restore-{}", + content_digest(&fs::read_file(&tool_manifest)?) + )); + command.inputs.push(CacheInput::FileHash(tool_manifest)); + + output.commands.push(command); + } + + Ok(Json(output)) +} diff --git a/toolchains/dotnet/src/tier3.rs b/toolchains/dotnet/src/tier3.rs new file mode 100644 index 00000000..a87af2bd --- /dev/null +++ b/toolchains/dotnet/src/tier3.rs @@ -0,0 +1,239 @@ +use crate::config::DotnetToolchainConfig; +use crate::discovery::{SKIP_DIRS, installed_sdk_versions}; +use crate::dotnet_install::{ + exact_version, install_script_file_name, install_script_url, install_version_args, +}; +use crate::global_json::{parse_sdk_requirement, satisfies}; +use extism_pdk::*; +use moon_pdk::{ + HostLogInput, HostLogTarget, exec, fetch_text, get_host_environment, host_log, + into_virtual_path, parse_toolchain_config, plugin_err, +}; +use moon_pdk_api::*; +use starbase_utils::fs; + +#[host_fn] +extern "ExtismHost" { + fn host_log(input: Json); +} + +/// Collect `global.json` files in the workspace (depth-limited). Unlike task +/// environments, setup has no project to walk up from — and the pin often +/// lives in a subtree (`src/backend/global.json`) rather than at the root. +fn collect_global_json_files(dir: &VirtualPath, depth: u8, out: &mut Vec) { + let Ok(entries) = fs::read_dir(dir.any_path()) else { + return; + }; + + let mut subdirs = vec![]; + + for entry in entries { + let Ok(name) = entry.file_name().into_string() else { + continue; + }; + + if entry.file_type().is_ok_and(|kind| kind.is_dir()) { + if depth > 0 + && !SKIP_DIRS + .iter() + .any(|skip| skip.eq_ignore_ascii_case(&name)) + { + subdirs.push(name); + } + } else if name.eq_ignore_ascii_case("global.json") { + out.push(dir.join(&name)); + } + } + + for name in subdirs { + collect_global_json_files(&dir.join(name), depth - 1, out); + } +} + +/// Warn when the SDKs now present in the install root cannot serve a +/// `global.json` pin in the workspace. Installing 8.0 while a subtree pins +/// 10.x is a silent misconfiguration otherwise: setup succeeds and every task +/// in that subtree fails later with the host's own error. +/// +/// A warning rather than an error: the pinned subtree may deliberately rely +/// on a system-wide SDK instead of the one moon manages. +fn warn_on_unsatisfied_pins( + workspace_root: &VirtualPath, + install_root: &VirtualPath, +) -> AnyResult<()> { + let mut files = vec![]; + collect_global_json_files(workspace_root, 4, &mut files); + + if files.is_empty() { + return Ok(()); + } + + let installed = installed_sdk_versions(install_root); + + for file in files { + let Ok(content) = fs::read_file(&file) else { + continue; + }; + + let Some(requirement) = parse_sdk_requirement(&content) else { + continue; + }; + + if !satisfies(&installed, &requirement) { + host_log!( + warn, + "{} pins .NET SDK {}, which the installed SDKs do not satisfy ({}). Tasks under that directory will fail until the pinned SDK is installed — set version to a matching value.", + file, + requirement.version, + if installed.is_empty() { + "none installed".to_owned() + } else { + installed.join(", ") + } + ); + } + } + + Ok(()) +} + +#[plugin_fn] +pub fn setup_toolchain( + Json(input): Json, +) -> FnResult> { + let mut output = SetupToolchainOutput::default(); + + // Without a `version:` setting moon skips the setup action entirely + // ("use globals on PATH"); stay a no-op if called anyway. + let Some(spec) = &input.configured_version else { + return Ok(Json(output)); + }; + + let config = parse_toolchain_config::(input.toolchain_config)?; + let env = get_host_environment()?; + let windows = env.os.is_windows(); + + // Install root: explicit `dotnetRoot` config wins, else `~/.dotnet` — + // the same order resolve_dotnet_root uses when injecting DOTNET_ROOT + // into task environments, so installed SDKs are picked up without any + // further configuration. SDK versions install side-by-side. + let install_root: std::path::PathBuf = match &config.dotnet_root { + Some(root) => root.into(), + None => { + let Some(home) = env.home_dir.real_path() else { + return Err(plugin_err!( + "Unable to resolve the host home directory for the default `~/.dotnet` install root." + )); + }; + + home.join(".dotnet") + } + }; + + let version_args = match install_version_args(spec, windows) { + Ok(args) => args, + Err(message) => return Err(plugin_err!("{}", message)), + }; + + // Fully-qualified versions can skip the network entirely when that SDK + // is already laid out. Channels/aliases resolve server-side, so the + // install script decides for those (it skips re-installs itself). + if let Some(version) = exact_version(spec) + && into_virtual_path(install_root.join("sdk").join(&version))?.exists() + { + warn_on_unsatisfied_pins( + &input.context.workspace_root, + &into_virtual_path(&install_root)?, + )?; + + return Ok(Json(output)); + } + + // Stage the official install script under moon's cache dir, fetched once. + // moon does not fingerprint-cache this action — `setup_toolchain` uses + // `create_hash_and_return_lock`, which unlike the `_if_changed` variant has + // no "manifest exists, skip" short-circuit — so this function runs on every + // moon invocation. Re-downloading each time made every command depend on + // reaching dot.net, which breaks offline and air-gapped workspaces + // outright. Delete the file to force a re-fetch. + let script_file = input + .context + .workspace_root + .join(".moon/cache/dotnet-toolchain") + .join(install_script_file_name(windows)); + + if !script_file.exists() { + if let Some(parent) = script_file.parent() { + fs::create_dir_all(parent)?; + } + + fs::write_file(&script_file, fetch_text(install_script_url(windows))?)?; + } + + let Some(script_path) = script_file.real_path() else { + return Err(plugin_err!( + "Unable to resolve a host path for the staged install script." + )); + }; + + // `--no-path`: task environments get DOTNET_ROOT/PATH injected by + // extend_task_command; the user's shell profile is left alone. + let mut args: Vec = if windows { + vec![ + "-NoProfile".into(), + "-ExecutionPolicy".into(), + "Bypass".into(), + "-File".into(), + script_path.to_string_lossy().to_string(), + "-InstallDir".into(), + install_root.to_string_lossy().to_string(), + "-NoPath".into(), + ] + } else { + vec![ + script_path.to_string_lossy().to_string(), + "--install-dir".into(), + install_root.to_string_lossy().to_string(), + "--no-path".into(), + ] + }; + + args.extend(version_args); + + let command = if windows { "powershell.exe" } else { "bash" }; + + // Known limitation: for a channel or alias (`version: '8.0'`, `'lts'`) the + // script still runs on every invocation, because only the server can say + // which patch a channel currently resolves to. It exits early once that SDK + // is present, but it does need the network to find out. Pin a + // fully-qualified version to take the exact-version path above and skip + // this entirely. + let mut operation = Operation::new("install-sdk")?; + let result = exec(ExecCommandInput::pipe(command, args))?; + + if result.exit_code != 0 { + operation.finish(OperationStatus::Failed); + output.operations.push(operation); + + return Err(plugin_err!( + "dotnet-install failed with exit code {}:\n{}\n{}", + result.exit_code, + result.stdout, + result.stderr, + )); + } + + operation.finish(OperationStatus::Passed); + output.operations.push(operation); + + warn_on_unsatisfied_pins( + &input.context.workspace_root, + &into_virtual_path(&install_root)?, + )?; + + // Informational only: for WASM-only toolchains the host currently + // derives the action status itself and merges just operations/files. + output.installed = true; + + Ok(Json(output)) +} diff --git a/toolchains/dotnet/tests/__fixtures__/cpm/Directory.Packages.props b/toolchains/dotnet/tests/__fixtures__/cpm/Directory.Packages.props new file mode 100644 index 00000000..ed592163 --- /dev/null +++ b/toolchains/dotnet/tests/__fixtures__/cpm/Directory.Packages.props @@ -0,0 +1,8 @@ + + + true + + + + + diff --git a/toolchains/dotnet/tests/__fixtures__/cpm/proj/Class1.cs b/toolchains/dotnet/tests/__fixtures__/cpm/proj/Class1.cs new file mode 100644 index 00000000..4e32e62e --- /dev/null +++ b/toolchains/dotnet/tests/__fixtures__/cpm/proj/Class1.cs @@ -0,0 +1,3 @@ +namespace Cpm; + +public class Class1; diff --git a/toolchains/dotnet/tests/__fixtures__/cpm/proj/Cpm.csproj b/toolchains/dotnet/tests/__fixtures__/cpm/proj/Cpm.csproj new file mode 100644 index 00000000..95ba4f5a --- /dev/null +++ b/toolchains/dotnet/tests/__fixtures__/cpm/proj/Cpm.csproj @@ -0,0 +1,8 @@ + + + net8.0 + + + + + diff --git a/toolchains/dotnet/tests/__fixtures__/locate-no-sln/proj/Proj.csproj b/toolchains/dotnet/tests/__fixtures__/locate-no-sln/proj/Proj.csproj new file mode 100644 index 00000000..ec2cce14 --- /dev/null +++ b/toolchains/dotnet/tests/__fixtures__/locate-no-sln/proj/Proj.csproj @@ -0,0 +1,5 @@ + + + net8.0 + + diff --git a/toolchains/dotnet/tests/__fixtures__/locate/Root.sln b/toolchains/dotnet/tests/__fixtures__/locate/Root.sln new file mode 100644 index 00000000..0e909292 --- /dev/null +++ b/toolchains/dotnet/tests/__fixtures__/locate/Root.sln @@ -0,0 +1,2 @@ +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 diff --git a/toolchains/dotnet/tests/__fixtures__/locate/nested/proj/Proj.csproj b/toolchains/dotnet/tests/__fixtures__/locate/nested/proj/Proj.csproj new file mode 100644 index 00000000..ec2cce14 --- /dev/null +++ b/toolchains/dotnet/tests/__fixtures__/locate/nested/proj/Proj.csproj @@ -0,0 +1,5 @@ + + + net8.0 + + diff --git a/toolchains/dotnet/tests/__fixtures__/locked/proj/Class1.cs b/toolchains/dotnet/tests/__fixtures__/locked/proj/Class1.cs new file mode 100644 index 00000000..c0bd9ca5 --- /dev/null +++ b/toolchains/dotnet/tests/__fixtures__/locked/proj/Class1.cs @@ -0,0 +1,6 @@ +namespace Locked; + +public static class Class1 +{ + public static string Json() => Newtonsoft.Json.JsonConvert.SerializeObject(new { ok = true }); +} diff --git a/toolchains/dotnet/tests/__fixtures__/locked/proj/Locked.csproj b/toolchains/dotnet/tests/__fixtures__/locked/proj/Locked.csproj new file mode 100644 index 00000000..933d733e --- /dev/null +++ b/toolchains/dotnet/tests/__fixtures__/locked/proj/Locked.csproj @@ -0,0 +1,9 @@ + + + net8.0 + enable + + + + + diff --git a/toolchains/dotnet/tests/__fixtures__/locked/proj/packages.lock.json b/toolchains/dotnet/tests/__fixtures__/locked/proj/packages.lock.json new file mode 100644 index 00000000..edc62aba --- /dev/null +++ b/toolchains/dotnet/tests/__fixtures__/locked/proj/packages.lock.json @@ -0,0 +1,13 @@ +{ + "version": 1, + "dependencies": { + "net8.0": { + "Newtonsoft.Json": { + "type": "Direct", + "requested": "[13.0.3, )", + "resolved": "13.0.3", + "contentHash": "HrC5BXdl00IP9zeV+0Z848QWPAoCr9P3bDEZguI+gkLcBKAOxix/tLEAAHC+UvDNPv4a2d18lOReHMOagPa+zQ==" + } + } + } +} \ No newline at end of file diff --git a/toolchains/dotnet/tests/__fixtures__/matrix/Directory.Build.props b/toolchains/dotnet/tests/__fixtures__/matrix/Directory.Build.props new file mode 100644 index 00000000..5c14cf23 --- /dev/null +++ b/toolchains/dotnet/tests/__fixtures__/matrix/Directory.Build.props @@ -0,0 +1,8 @@ + + + 12 + + + + + diff --git a/toolchains/dotnet/tests/__fixtures__/matrix/cond/Class1.cs b/toolchains/dotnet/tests/__fixtures__/matrix/cond/Class1.cs new file mode 100644 index 00000000..b3547b3e --- /dev/null +++ b/toolchains/dotnet/tests/__fixtures__/matrix/cond/Class1.cs @@ -0,0 +1,3 @@ +namespace Cond; + +public class Class1; diff --git a/toolchains/dotnet/tests/__fixtures__/matrix/cond/Cond.csproj b/toolchains/dotnet/tests/__fixtures__/matrix/cond/Cond.csproj new file mode 100644 index 00000000..66fccc33 --- /dev/null +++ b/toolchains/dotnet/tests/__fixtures__/matrix/cond/Cond.csproj @@ -0,0 +1,11 @@ + + + net8.0 + 1 + + + + + + + diff --git a/toolchains/dotnet/tests/__fixtures__/matrix/multi/Class1.cs b/toolchains/dotnet/tests/__fixtures__/matrix/multi/Class1.cs new file mode 100644 index 00000000..dc5020eb --- /dev/null +++ b/toolchains/dotnet/tests/__fixtures__/matrix/multi/Class1.cs @@ -0,0 +1,5 @@ +namespace Multi; + +public class Class1 +{ +} diff --git a/toolchains/dotnet/tests/__fixtures__/matrix/multi/Multi.csproj b/toolchains/dotnet/tests/__fixtures__/matrix/multi/Multi.csproj new file mode 100644 index 00000000..61914e6f --- /dev/null +++ b/toolchains/dotnet/tests/__fixtures__/matrix/multi/Multi.csproj @@ -0,0 +1,10 @@ + + + net8.0;netstandard2.0 + + + + + + diff --git a/toolchains/dotnet/tests/__fixtures__/matrix/nested/Directory.Build.props b/toolchains/dotnet/tests/__fixtures__/matrix/nested/Directory.Build.props new file mode 100644 index 00000000..2bf8a608 --- /dev/null +++ b/toolchains/dotnet/tests/__fixtures__/matrix/nested/Directory.Build.props @@ -0,0 +1,7 @@ + + + + + + + diff --git a/toolchains/dotnet/tests/__fixtures__/matrix/nested/deep/Class1.cs b/toolchains/dotnet/tests/__fixtures__/matrix/nested/deep/Class1.cs new file mode 100644 index 00000000..ea7802bc --- /dev/null +++ b/toolchains/dotnet/tests/__fixtures__/matrix/nested/deep/Class1.cs @@ -0,0 +1,3 @@ +namespace Deep; + +public class Class1; diff --git a/toolchains/dotnet/tests/__fixtures__/matrix/nested/deep/Deep.csproj b/toolchains/dotnet/tests/__fixtures__/matrix/nested/deep/Deep.csproj new file mode 100644 index 00000000..81fb97b0 --- /dev/null +++ b/toolchains/dotnet/tests/__fixtures__/matrix/nested/deep/Deep.csproj @@ -0,0 +1,6 @@ + + + MyCompany.Deep + net8.0 + + diff --git a/toolchains/dotnet/tests/__fixtures__/mixed-lang/app/App.csproj b/toolchains/dotnet/tests/__fixtures__/mixed-lang/app/App.csproj new file mode 100644 index 00000000..c563353f --- /dev/null +++ b/toolchains/dotnet/tests/__fixtures__/mixed-lang/app/App.csproj @@ -0,0 +1,10 @@ + + + Exe + net8.0 + enable + + + + + diff --git a/toolchains/dotnet/tests/__fixtures__/mixed-lang/app/Program.cs b/toolchains/dotnet/tests/__fixtures__/mixed-lang/app/Program.cs new file mode 100644 index 00000000..63d1d2cf --- /dev/null +++ b/toolchains/dotnet/tests/__fixtures__/mixed-lang/app/Program.cs @@ -0,0 +1 @@ +Console.WriteLine(Lib.Say.hello()); diff --git a/toolchains/dotnet/tests/__fixtures__/mixed-lang/core/Class1.vb b/toolchains/dotnet/tests/__fixtures__/mixed-lang/core/Class1.vb new file mode 100644 index 00000000..f0cb2e9c --- /dev/null +++ b/toolchains/dotnet/tests/__fixtures__/mixed-lang/core/Class1.vb @@ -0,0 +1,2 @@ +Public Class Class1 +End Class diff --git a/toolchains/dotnet/tests/__fixtures__/mixed-lang/core/Core.vbproj b/toolchains/dotnet/tests/__fixtures__/mixed-lang/core/Core.vbproj new file mode 100644 index 00000000..e489be62 --- /dev/null +++ b/toolchains/dotnet/tests/__fixtures__/mixed-lang/core/Core.vbproj @@ -0,0 +1,6 @@ + + + Core + net8.0 + + diff --git a/toolchains/dotnet/tests/__fixtures__/mixed-lang/lib/Lib.fsproj b/toolchains/dotnet/tests/__fixtures__/mixed-lang/lib/Lib.fsproj new file mode 100644 index 00000000..87a10718 --- /dev/null +++ b/toolchains/dotnet/tests/__fixtures__/mixed-lang/lib/Lib.fsproj @@ -0,0 +1,11 @@ + + + net8.0 + + + + + + + + diff --git a/toolchains/dotnet/tests/__fixtures__/mixed-lang/lib/Library.fs b/toolchains/dotnet/tests/__fixtures__/mixed-lang/lib/Library.fs new file mode 100644 index 00000000..6b6340f4 --- /dev/null +++ b/toolchains/dotnet/tests/__fixtures__/mixed-lang/lib/Library.fs @@ -0,0 +1,4 @@ +namespace Lib + +module Say = + let hello () = "hello" diff --git a/toolchains/dotnet/tests/__fixtures__/mtp/global.json b/toolchains/dotnet/tests/__fixtures__/mtp/global.json new file mode 100644 index 00000000..3140116d --- /dev/null +++ b/toolchains/dotnet/tests/__fixtures__/mtp/global.json @@ -0,0 +1,5 @@ +{ + "test": { + "runner": "Microsoft.Testing.Platform" + } +} diff --git a/toolchains/dotnet/tests/__fixtures__/mtp/suite/Helpers.csproj b/toolchains/dotnet/tests/__fixtures__/mtp/suite/Helpers.csproj new file mode 100644 index 00000000..ec2cce14 --- /dev/null +++ b/toolchains/dotnet/tests/__fixtures__/mtp/suite/Helpers.csproj @@ -0,0 +1,5 @@ + + + net8.0 + + diff --git a/toolchains/dotnet/tests/__fixtures__/mtp/suite/Suite.Tests.csproj b/toolchains/dotnet/tests/__fixtures__/mtp/suite/Suite.Tests.csproj new file mode 100644 index 00000000..bcef8366 --- /dev/null +++ b/toolchains/dotnet/tests/__fixtures__/mtp/suite/Suite.Tests.csproj @@ -0,0 +1,11 @@ + + + net8.0 + + Exe + true + + + + + diff --git a/toolchains/dotnet/tests/__fixtures__/mtp/suite/UnitTest1.cs b/toolchains/dotnet/tests/__fixtures__/mtp/suite/UnitTest1.cs new file mode 100644 index 00000000..c9418ae6 --- /dev/null +++ b/toolchains/dotnet/tests/__fixtures__/mtp/suite/UnitTest1.cs @@ -0,0 +1,6 @@ +public class UnitTest1 +{ + public void Passes() + { + } +} diff --git a/toolchains/dotnet/tests/__fixtures__/projects/app-tests/App.Tests.csproj b/toolchains/dotnet/tests/__fixtures__/projects/app-tests/App.Tests.csproj new file mode 100644 index 00000000..4e363e65 --- /dev/null +++ b/toolchains/dotnet/tests/__fixtures__/projects/app-tests/App.Tests.csproj @@ -0,0 +1,13 @@ + + + net8.0 + enable + false + + + + + + + + diff --git a/toolchains/dotnet/tests/__fixtures__/projects/app-tests/UnitTest1.cs b/toolchains/dotnet/tests/__fixtures__/projects/app-tests/UnitTest1.cs new file mode 100644 index 00000000..21c1b23a --- /dev/null +++ b/toolchains/dotnet/tests/__fixtures__/projects/app-tests/UnitTest1.cs @@ -0,0 +1,9 @@ +using Xunit; + +namespace App.Tests; + +public class UnitTest1 +{ + [Fact] + public void Passes() => Assert.True(true); +} diff --git a/toolchains/dotnet/tests/__fixtures__/projects/app/App.csproj b/toolchains/dotnet/tests/__fixtures__/projects/app/App.csproj new file mode 100644 index 00000000..d1d8dc7a --- /dev/null +++ b/toolchains/dotnet/tests/__fixtures__/projects/app/App.csproj @@ -0,0 +1,11 @@ + + + Exe + net8.0 + enable + enable + + + + + diff --git a/toolchains/dotnet/tests/__fixtures__/projects/app/Program.cs b/toolchains/dotnet/tests/__fixtures__/projects/app/Program.cs new file mode 100644 index 00000000..1d8b652f --- /dev/null +++ b/toolchains/dotnet/tests/__fixtures__/projects/app/Program.cs @@ -0,0 +1 @@ +Console.WriteLine(Lib.Class1.Hello()); diff --git a/toolchains/dotnet/tests/__fixtures__/projects/core/Class1.cs b/toolchains/dotnet/tests/__fixtures__/projects/core/Class1.cs new file mode 100644 index 00000000..4cbf7427 --- /dev/null +++ b/toolchains/dotnet/tests/__fixtures__/projects/core/Class1.cs @@ -0,0 +1,6 @@ +namespace Core; + +public static class Class1 +{ + public static string Value() => "hello"; +} diff --git a/toolchains/dotnet/tests/__fixtures__/projects/core/Core.csproj b/toolchains/dotnet/tests/__fixtures__/projects/core/Core.csproj new file mode 100644 index 00000000..ad1b47cd --- /dev/null +++ b/toolchains/dotnet/tests/__fixtures__/projects/core/Core.csproj @@ -0,0 +1,6 @@ + + + net8.0 + enable + + diff --git a/toolchains/dotnet/tests/__fixtures__/projects/lib/Class1.cs b/toolchains/dotnet/tests/__fixtures__/projects/lib/Class1.cs new file mode 100644 index 00000000..006dbb05 --- /dev/null +++ b/toolchains/dotnet/tests/__fixtures__/projects/lib/Class1.cs @@ -0,0 +1,6 @@ +namespace Lib; + +public static class Class1 +{ + public static string Hello() => Core.Class1.Value(); +} diff --git a/toolchains/dotnet/tests/__fixtures__/projects/lib/Lib.csproj b/toolchains/dotnet/tests/__fixtures__/projects/lib/Lib.csproj new file mode 100644 index 00000000..6cc721a2 --- /dev/null +++ b/toolchains/dotnet/tests/__fixtures__/projects/lib/Lib.csproj @@ -0,0 +1,9 @@ + + + net8.0 + enable + + + + + diff --git a/toolchains/dotnet/tests/__fixtures__/unevaluatable/proj/Broken.csproj b/toolchains/dotnet/tests/__fixtures__/unevaluatable/proj/Broken.csproj new file mode 100644 index 00000000..f8bab3ef --- /dev/null +++ b/toolchains/dotnet/tests/__fixtures__/unevaluatable/proj/Broken.csproj @@ -0,0 +1,9 @@ + + + net8.0 + + + + diff --git a/toolchains/dotnet/tests/infer_tasks_test.rs b/toolchains/dotnet/tests/infer_tasks_test.rs new file mode 100644 index 00000000..1e0e6650 --- /dev/null +++ b/toolchains/dotnet/tests/infer_tasks_test.rs @@ -0,0 +1,521 @@ +use dotnet_toolchain::config::InferTasksSetting; +use dotnet_toolchain::infer_tasks::*; +use dotnet_toolchain::msbuild::MsbuildEvaluation; +use moon_common::Id; +use moon_config::{ + Input, Output, PartialTaskArgs, PartialTaskConfig, PartialTaskDependency, + PartialTaskDependencyConfig, TaskOptionCache, TaskOptionRunInCI, +}; +use moon_target::Target; +use std::collections::{BTreeMap, BTreeSet}; + +mod infer_tasks { + use super::*; + + fn evaluation(properties: &[(&str, &str)]) -> MsbuildEvaluation { + let mut evaluation = MsbuildEvaluation::default(); + + for (name, value) in properties { + evaluation + .properties + .insert(name.to_string(), value.to_string()); + } + + evaluation + } + + fn infer( + evaluation: &MsbuildEvaluation, + setting: &InferTasksSetting, + reserved: &[&str], + ) -> BTreeMap { + infer_tasks( + setting, + &reserved.iter().map(|id| id.to_string()).collect(), + &InferInputs { + evaluation, + explicit_project_file: None, + project_dir: "C:\\work\\repo\\app", + workspace_dir: "C:\\work\\repo", + test_platform_runner: false, + }, + ) + .unwrap() + } + + fn test_project_evaluation() -> MsbuildEvaluation { + let mut eval = evaluation(&[("OutputType", "Exe"), ("TargetFramework", "net10.0")]); + eval.items.insert( + "PackageReference".into(), + vec![serde_json::json!({ "Identity": "Microsoft.NET.Test.Sdk" })], + ); + eval + } + + /// An `Exe` with the given package references and no test-related property, + /// mirroring what an unrestored tree reports. + fn exe_with_packages(packages: &[&str]) -> MsbuildEvaluation { + let mut eval = evaluation(&[("OutputType", "Exe"), ("TargetFramework", "net10.0")]); + eval.items.insert( + "PackageReference".into(), + packages + .iter() + .map(|name| serde_json::json!({ "Identity": name })) + .collect(), + ); + eval + } + + fn command_line(task: &PartialTaskConfig) -> String { + match task.command.as_ref().unwrap() { + PartialTaskArgs::List(list) => list.join(" "), + PartialTaskArgs::String(value) => value.clone(), + other => panic!("unexpected command shape: {other:?}"), + } + } + + #[test] + fn classlib_gets_build_only() { + let eval = evaluation(&[ + ("OutputType", "Library"), + ("BaseOutputPath", "bin\\"), + ("BaseIntermediateOutputPath", "obj\\"), + ]); + let tasks = infer(&eval, &InferTasksSetting::default(), &[]); + + assert_eq!( + tasks.keys().map(|id| id.as_str()).collect::>(), + vec!["build"] + ); + + let build = &tasks[&Id::raw("build")]; + assert_eq!( + command_line(build), + "dotnet build --no-restore --no-dependencies" + ); + assert_eq!( + build.outputs.as_ref().unwrap(), + &vec![Output::parse("bin").unwrap()] + ); + assert!(build.options.is_none(), "outputs known => cache untouched"); + assert!(build.deps.is_some()); + // Inputs exclude the evaluated output/intermediate dirs so hashes + // stabilize (obj is mutated by every build). + assert_eq!( + build.inputs.as_ref().unwrap(), + &vec![ + Input::parse("**/*").unwrap(), + Input::parse("!bin/**").unwrap(), + Input::parse("!obj/**").unwrap(), + ] + ); + } + + #[test] + fn exe_gets_build_run_publish() { + let eval = evaluation(&[ + ("OutputType", "Exe"), + ("BaseOutputPath", "bin\\"), + ("TargetFramework", "net8.0"), + ("PublishDir", "bin\\Debug\\net8.0\\publish\\"), + ]); + let tasks = infer(&eval, &InferTasksSetting::default(), &[]); + + assert_eq!( + tasks.keys().map(|id| id.as_str()).collect::>(), + vec!["build", "publish", "run"] + ); + + let run = &tasks[&Id::raw("run")]; + assert_eq!(command_line(run), "dotnet run"); + let run_options = run.options.as_ref().unwrap(); + assert_eq!(run_options.cache, Some(TaskOptionCache::Enabled(false))); + assert_eq!( + run_options.run_in_ci, + Some(TaskOptionRunInCI::Enabled(false)) + ); + + let publish = &tasks[&Id::raw("publish")]; + assert_eq!( + command_line(publish), + "dotnet publish --no-build --no-restore" + ); + assert_eq!( + publish.outputs.as_ref().unwrap(), + &vec![Output::parse("bin/Debug/net8.0/publish").unwrap()] + ); + } + + #[test] + fn test_project_gets_build_test_never_run() { + // Modern test SDKs can flip OutputType to Exe — test wins over run. + let mut eval = evaluation(&[("OutputType", "Exe"), ("TargetFramework", "net8.0")]); + eval.items.insert( + "PackageReference".into(), + vec![serde_json::json!({ "Identity": "Microsoft.NET.Test.Sdk", "Version": "17.10.0" })], + ); + + let tasks = infer(&eval, &InferTasksSetting::default(), &[]); + + assert!(tasks.contains_key(&Id::raw("build"))); + assert!(tasks.contains_key(&Id::raw("test"))); + assert!(!tasks.contains_key(&Id::raw("run"))); + assert!(!tasks.contains_key(&Id::raw("publish"))); + + let test = &tasks[&Id::raw("test")]; + assert_eq!(command_line(test), "dotnet test --no-build --no-restore"); + } + + #[test] + fn pins_evaluated_configuration_on_cacheable_commands() { + // `dotnet publish` defaults to Release (.NET 8+) while `dotnet build` + // defaults to Debug — the explicit `-c` keeps `--no-build` coherent. + let mut eval = evaluation(&[ + ("OutputType", "Exe"), + ("TargetFramework", "net8.0"), + ("Configuration", "Debug"), + ("PublishDir", "bin\\Debug\\net8.0\\publish\\"), + ]); + eval.items.insert( + "PackageReference".into(), + vec![serde_json::json!({ "Identity": "Microsoft.NET.Test.Sdk" })], + ); + + let tasks = infer(&eval, &InferTasksSetting::default(), &[]); + + assert_eq!( + command_line(&tasks[&Id::raw("build")]), + "dotnet build --no-restore --no-dependencies -c Debug" + ); + assert_eq!( + command_line(&tasks[&Id::raw("test")]), + "dotnet test --no-build --no-restore -c Debug" + ); + } + + #[test] + fn multi_tfm_exe_skips_publish() { + let eval = evaluation(&[("OutputType", "Exe"), ("TargetFrameworks", "net8.0;net9.0")]); + let tasks = infer(&eval, &InferTasksSetting::default(), &[]); + + assert!(tasks.contains_key(&Id::raw("run"))); + assert!(!tasks.contains_key(&Id::raw("publish"))); + } + + #[test] + fn unknown_outputs_disable_caching_instead_of_guessing() { + // BaseOutputPath redirected outside the workspace entirely. + let eval = evaluation(&[ + ("OutputType", "Library"), + ("BaseOutputPath", "D:\\global-outputs\\app\\"), + ]); + let tasks = infer(&eval, &InferTasksSetting::default(), &[]); + + let build = &tasks[&Id::raw("build")]; + assert!(build.outputs.is_none()); + assert_eq!( + build.options.as_ref().unwrap().cache, + Some(TaskOptionCache::Enabled(false)) + ); + } + + #[test] + fn granular_selection_and_reserved_ids_are_respected() { + let eval = evaluation(&[ + ("OutputType", "Exe"), + ("BaseOutputPath", "bin\\"), + ("TargetFramework", "net8.0"), + ("PublishDir", "bin\\Debug\\net8.0\\publish\\"), + ]); + + let only = InferTasksSetting::Only(vec!["run".into(), "publish".into()]); + let tasks = infer(&eval, &only, &[]); + assert_eq!( + tasks.keys().map(|id| id.as_str()).collect::>(), + vec!["publish", "run"], + "granular selection" + ); + + let tasks = infer(&eval, &InferTasksSetting::default(), &["run", "build"]); + assert_eq!( + tasks.keys().map(|id| id.as_str()).collect::>(), + vec!["publish"], + "reserved (inherited) ids skipped" + ); + + let tasks = infer(&eval, &InferTasksSetting::Enabled(false), &[]); + assert!(tasks.is_empty()); + } + + #[test] + fn detects_test_projects_that_have_no_microsoft_net_test_sdk() { + // Microsoft.Testing.Platform test projects replace Microsoft.NET.Test.Sdk + // outright. Shapes taken from real repositories: dotnet/eShop uses + // ``, which sets the property and references no + // test package; OrchardCMS/OrchardCore uses `xunit.v3.mtp-v2` with + // neither property set on an unrestored tree. + let by_property = evaluation(&[ + ("OutputType", "Exe"), + ("TargetFramework", "net10.0"), + ("IsTestingPlatformApplication", "true"), + ]); + + for (label, eval) in [ + ("MSTest.Sdk property", by_property), + ("xunit.v3 package", exe_with_packages(&["xunit.v3.mtp-v2"])), + ( + "platform package", + exe_with_packages(&["Microsoft.Testing.Platform.MSBuild"]), + ), + ] { + let tasks = infer(&eval, &InferTasksSetting::default(), &[]); + let ids = tasks.keys().map(|id| id.as_str()).collect::>(); + + assert!( + ids.contains(&"test"), + "{label}: expected a test task, got {ids:?}" + ); + // A test project is not an application, so it gets neither of these. + assert!(!ids.contains(&"run"), "{label}: {ids:?}"); + assert!(!ids.contains(&"publish"), "{label}: {ids:?}"); + } + } + + #[test] + fn does_not_mistake_test_helper_packages_for_a_test_project() { + // All three appear in real test-adjacent projects. Matching "test" as a + // substring would wrongly flag every one of them, and a BenchmarkDotNet + // project explicitly sets the properties to `false`. + let eval = exe_with_packages(&[ + "Microsoft.AspNetCore.Mvc.Testing", + "Microsoft.AspNetCore.TestHost", + "BenchmarkDotNet", + ]); + + let tasks = infer(&eval, &InferTasksSetting::default(), &[]); + let ids = tasks.keys().map(|id| id.as_str()).collect::>(); + + assert!(!ids.contains(&"test"), "{ids:?}"); + assert!( + ids.contains(&"run"), + "an executable must still get run: {ids:?}" + ); + + let benchmarks = evaluation(&[ + ("OutputType", "Exe"), + ("TargetFramework", "net10.0"), + ("IsTestProject", "false"), + ("IsTestingPlatformApplication", "false"), + ]); + let tasks = infer(&benchmarks, &InferTasksSetting::default(), &[]); + + assert!(!tasks.keys().any(|id| id.as_str() == "test")); + } + + #[test] + fn the_self_build_dep_is_optional() { + // moon defaults `~:` deps to mandatory, so selecting only `test` or + // only `publish` — no `build` task to depend on — would fail + // project-graph construction with `UnknownDepTarget` if these were + // plain targets. + // A project is either a test project or an executable — `is_exe` + // excludes `is_test` — so each dep needs its own evaluation. + let cases = [ + ("test", evaluation(&[("IsTestProject", "true")])), + ( + "publish", + evaluation(&[ + ("OutputType", "Exe"), + ("TargetFramework", "net8.0"), + ("PublishDir", "bin\\Debug\\net8.0\\publish\\"), + ]), + ), + ]; + + for (id, eval) in cases { + let tasks = infer(&eval, &InferTasksSetting::Only(vec![id.into()]), &[]); + + assert_eq!( + tasks[&Id::raw(id)].deps.as_deref(), + Some( + &[PartialTaskDependency::Object(PartialTaskDependencyConfig { + target: Some(Target::parse("~:build").unwrap()), + optional: Some(true), + ..Default::default() + })][..] + ), + "`{id}` must depend on an optional `~:build`" + ); + } + } + + #[test] + fn multiple_project_files_get_explicit_targets() { + let eval = evaluation(&[ + ("OutputType", "Exe"), + ("BaseOutputPath", "bin\\"), + ("TargetFramework", "net8.0"), + ]); + + let tasks = infer_tasks( + &InferTasksSetting::default(), + &BTreeSet::new(), + &InferInputs { + evaluation: &eval, + explicit_project_file: Some("App.csproj"), + project_dir: "/repo/app", + workspace_dir: "/repo", + test_platform_runner: false, + }, + ) + .unwrap(); + + assert_eq!( + command_line(&tasks[&Id::raw("build")]), + "dotnet build App.csproj --no-restore --no-dependencies" + ); + assert_eq!( + command_line(&tasks[&Id::raw("run")]), + "dotnet run --project App.csproj" + ); + } + + #[test] + fn test_platform_takes_the_project_through_a_flag() { + let eval = test_project_evaluation(); + + let infer_with = |runner: bool, file: Option<&str>| { + let tasks = infer_tasks( + &InferTasksSetting::default(), + &BTreeSet::new(), + &InferInputs { + evaluation: &eval, + explicit_project_file: file, + project_dir: "/repo/app-tests", + workspace_dir: "/repo", + test_platform_runner: runner, + }, + ) + .unwrap(); + + command_line(&tasks[&Id::raw("test")]) + }; + + // MTP rejects a positional project path... + assert_eq!( + infer_with(true, Some("App.Tests.csproj")), + "dotnet test --project App.Tests.csproj --no-build --no-restore" + ); + // ...while classic VSTest mode rejects `--project`. + assert_eq!( + infer_with(false, Some("App.Tests.csproj")), + "dotnet test App.Tests.csproj --no-build --no-restore" + ); + // With one project file in the directory neither flavour applies: + // the command runs in the project directory with no path at all. + assert_eq!( + infer_with(true, None), + "dotnet test --no-build --no-restore" + ); + assert_eq!( + infer_with(false, None), + "dotnet test --no-build --no-restore" + ); + } + + #[test] + fn project_level_test_platform_opt_in_is_honored() { + // A project can select MTP on its own, without a global.json. + let mut eval = test_project_evaluation(); + eval.properties + .insert("TestingPlatformDotnetTestSupport".into(), "true".into()); + + let tasks = infer_tasks( + &InferTasksSetting::default(), + &BTreeSet::new(), + &InferInputs { + evaluation: &eval, + explicit_project_file: Some("App.Tests.csproj"), + project_dir: "/repo/app-tests", + workspace_dir: "/repo", + test_platform_runner: false, + }, + ) + .unwrap(); + + assert_eq!( + command_line(&tasks[&Id::raw("test")]), + "dotnet test --project App.Tests.csproj --no-build --no-restore" + ); + } + + #[test] + fn reports_only_conflicts_that_actually_suppress_inference() { + let reserved: BTreeMap = [ + ("build", "/workspace/.moon/tasks/dotnet.yml"), + ("publish", "/workspace/.moon/tasks.yml"), + // Not inferable, so its presence is unremarkable. + ("lint", "/workspace/.moon/tasks/all.yml"), + ] + .iter() + .map(|(id, file)| (id.to_string(), file.to_string())) + .collect(); + + assert_eq!( + reportable_conflicts(&reserved, &InferTasksSetting::default()), + vec![ + ("build", "/workspace/.moon/tasks/dotnet.yml"), + ("publish", "/workspace/.moon/tasks.yml"), + ] + ); + + // A task the user did not ask us to infer is not a conflict. + assert_eq!( + reportable_conflicts(&reserved, &InferTasksSetting::Only(vec!["publish".into()])), + vec![("publish", "/workspace/.moon/tasks.yml")] + ); + + assert!(reportable_conflicts(&reserved, &InferTasksSetting::Enabled(false)).is_empty()); + assert!(reportable_conflicts(&BTreeMap::new(), &InferTasksSetting::default()).is_empty()); + } + + #[test] + fn resolves_output_paths_in_every_form() { + // Relative stays relative. + assert_eq!( + resolve_output_path("bin\\", "C:\\repo\\app", "C:\\repo"), + Some("bin".into()) + ); + // Absolute under the project dir, case-insensitive. + assert_eq!( + resolve_output_path("C:\\Repo\\App\\bin\\Debug\\", "c:\\repo\\app", "c:\\repo"), + Some("bin/Debug".into()) + ); + // Absolute under the workspace (artifacts layout) => workspace-relative. + assert_eq!( + resolve_output_path( + "C:\\repo\\artifacts\\bin\\app\\", + "C:\\repo\\app", + "C:\\repo" + ), + Some("/artifacts/bin/app".into()) + ); + // Unix forms. + assert_eq!( + resolve_output_path("/repo/app/bin", "/repo/app", "/repo"), + Some("bin".into()) + ); + // Outside the workspace => not resolvable. + assert_eq!( + resolve_output_path("D:\\elsewhere\\bin", "C:\\repo\\app", "C:\\repo"), + None + ); + // Empty => not resolvable. + assert_eq!(resolve_output_path("", "C:\\repo\\app", "C:\\repo"), None); + // Prefix must respect component boundaries. + assert_eq!( + resolve_output_path("/repo/app-other/bin", "/repo/app", "/repo"), + Some("/app-other/bin".into()) + ); + } +} diff --git a/toolchains/dotnet/tests/msbuild_batch_test.rs b/toolchains/dotnet/tests/msbuild_batch_test.rs new file mode 100644 index 00000000..460ade2b --- /dev/null +++ b/toolchains/dotnet/tests/msbuild_batch_test.rs @@ -0,0 +1,154 @@ +//! Requires a .NET SDK (8+) on `PATH`: these tests spawn a real +//! `dotnet msbuild` outside the wasm plugin, which is the point of the file — +//! the plugin's per-project fallback would silently mask a broken batch in the +//! sandbox tests. `-getItem` JSON output is what needs SDK 8+. + +use dotnet_toolchain::msbuild::{ + MsbuildEvaluation, detect_failed_projects, moon_eval_targets_xml, normalize_path_key, + parse_batch_output, traversal_project_xml, +}; +use starbase_sandbox::create_empty_sandbox; +use std::path::{Path, PathBuf}; +use std::process::{Command, Output}; + +mod msbuild { + use super::*; + + fn fixtures() -> PathBuf { + PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/__fixtures__/projects") + } + + /// Stage the generated traversal project alongside the injected targets file + /// and evaluate it, exactly as `evaluate_projects_batch` does. + fn run_batch(scratch: &Path, projects: &[String]) -> Output { + std::fs::write(scratch.join("moon-eval.targets"), moon_eval_targets_xml()).unwrap(); + std::fs::write( + scratch.join("traversal.proj"), + traversal_project_xml(projects), + ) + .unwrap(); + + Command::new("dotnet") + .args([ + "msbuild", + scratch.join("traversal.proj").to_str().unwrap(), + "-nologo", + "-maxCpuCount", + "-nodeReuse:false", + "-t:MoonCollect", + "-getItem:MoonEval", + ]) + .output() + .expect("failed to spawn `dotnet msbuild`") + } + + /// End-to-end validation of the batched evaluation mechanism against a real + /// MSBuild. + #[test] + fn batched_traversal_evaluates_fixture_projects() { + let sandbox = create_empty_sandbox(); + + let projects = [ + ["app", "App.csproj"], + ["lib", "Lib.csproj"], + ["core", "Core.csproj"], + ["app-tests", "App.Tests.csproj"], + ] + .iter() + .map(|[dir, file]| { + fixtures() + .join(dir) + .join(file) + .to_string_lossy() + .to_string() + }) + .collect::>(); + + let output = run_batch(sandbox.path(), &projects); + let stdout = String::from_utf8_lossy(&output.stdout); + + assert!( + output.status.success(), + "batch invocation failed ({:?}):\n{stdout}\n{}", + output.status, + String::from_utf8_lossy(&output.stderr), + ); + + let results = parse_batch_output(&stdout).unwrap(); + + let get = |index: usize| -> &MsbuildEvaluation { + let key = normalize_path_key(&projects[index]); + + results + .get(&key) + .unwrap_or_else(|| panic!("missing {key} in batch output: {:?}", results.keys())) + }; + + // app -> lib, and its Exe OutputType survives the round-trip. + let app = get(0); + let app_refs = app.project_reference_paths(); + assert_eq!(app_refs.len(), 1); + assert!(normalize_path_key(&app_refs[0]).ends_with("lib/lib.csproj")); + assert_eq!(app.property("OutputType"), "Exe"); + + // lib -> core. + let lib_refs = get(1).project_reference_paths(); + assert_eq!(lib_refs.len(), 1); + assert!(normalize_path_key(&lib_refs[0]).ends_with("core/core.csproj")); + + // core has no references at all. + let core = get(2); + assert!(core.project_reference_paths().is_empty()); + assert!(core.package_references().is_empty()); + + // app-tests -> app, with its evaluated package set intact. + let tests = get(3); + let tests_refs = tests.project_reference_paths(); + assert!(normalize_path_key(&tests_refs[0]).ends_with("app/app.csproj")); + + let packages = tests.package_references(); + assert_eq!(packages.get("Microsoft.NET.Test.Sdk").unwrap(), "17.10.0"); + assert_eq!(packages.get("xunit").unwrap(), "2.8.0"); + } + + /// Documents the MSBuild behavior the retry logic in + /// `evaluate_projects_batch` exists for: one unloadable project makes the + /// whole batch return exit != 0 with ZERO target outputs (`ContinueOnError` + /// does not rescue load errors) — and validates that + /// `detect_failed_projects` identifies exactly the offender from the real + /// error output, so the retry can exclude it. + #[test] + fn broken_project_aborts_batch_and_is_detectable() { + let sandbox = create_empty_sandbox(); + sandbox.create_file( + "broken/Broken.csproj", + " the workspace root. + assert_eq!( + common_source_prefix(&["src/backend/App", "tools/Generator"]), + "" + ); + + // A single project yields its own directory; separators normalize. + assert_eq!(common_source_prefix(&["apps\\api"]), "apps/api"); + assert_eq!(common_source_prefix(&["."]), ""); + assert_eq!(common_source_prefix(&[]), ""); + // No accidental partial-component matches. + assert_eq!(common_source_prefix(&["src/app", "src/app-other"]), "src"); + } + + #[test] + fn escapes_msbuild_includes() { + assert_eq!( + escape_msbuild_include("C:\\repo\\A & B\\$(odd)@*?;\"100%\".csproj"), + "C:\\repo\\A & B\\%24(odd)%40%2A%3F%3B<x>"100%25".csproj" + ); + } + + #[test] + fn targets_xml_covers_all_eval_properties() { + let xml = moon_eval_targets_xml(); + + for prop in EVAL_PROPERTIES.split(',') { + assert!( + xml.contains(&format!("<{prop}>$({prop})")), + "{prop}" + ); + } + + assert!(xml.contains("MoonProjectRefs")); + assert!(xml.contains("MoonPackageRefs")); + } + + #[test] + fn traversal_xml_lists_projects_and_injects_both_hooks() { + let xml = traversal_project_xml(&[ + "C:\\repo\\a\\a.csproj".to_string(), + "/home/x/b & c/b.csproj".to_string(), + ]); + + assert!(xml.contains("Include=\"C:\\repo\\a\\a.csproj\"")); + assert!(xml.contains("Include=\"/home/x/b & c/b.csproj\"")); + // Both hooks: plain SDK projects import CustomAfterMicrosoftCommonTargets, + // multi-TFM outer builds import the CrossTargeting variant instead. + assert!(xml.contains( + "CustomAfterMicrosoftCommonTargets=$(MSBuildThisFileDirectory)moon-eval.targets" + )); + assert!(xml.contains("CustomAfterMicrosoftCommonCrossTargetingTargets=$(MSBuildThisFileDirectory)moon-eval.targets")); + assert!(xml.contains("BuildInParallel=\"true\"")); + assert!(xml.contains("ContinueOnError=\"WarnAndContinue\"")); + } + + #[test] + fn recognizes_sdk_resolution_failures() { + // Real host output (abridged) from a workspace pinning an SDK that + // is not installed. + let output = "\ +5.0.100 [C:\\Program Files\\dotnet\\sdk] + A compatible .NET SDK was not found. + +Requested SDK version: 10.0.301 +global.json file: C:\\repo\\src\\backend\\global.json + +Learn about SDK resolution: +https://aka.ms/dotnet/sdk-not-found"; + + assert!(is_sdk_resolution_failure(output)); + // The URL alone is enough, so localized message text still matches. + assert!(is_sdk_resolution_failure( + "irgendein Fehler\nhttps://aka.ms/dotnet/sdk-not-found" + )); + + // A broken project is a different failure class and must not be + // reported as a missing SDK. + assert!(!is_sdk_resolution_failure( + "C:\\repo\\app\\App.csproj(1,41): error MSB4025: The project file could not be loaded." + )); + assert!(!is_sdk_resolution_failure( + "error MSB1009: Project file does not exist." + )); + } + + #[test] + fn detects_failed_projects_across_short_and_long_path_forms() { + // Real shape from GitHub's windows-latest runners: we pass a path + // with an 8.3 short-name prefix (from %TEMP%), MSBuild's error line + // prints the expanded long form. + let output = "C:\\Users\\runneradmin\\AppData\\Local\\Temp\\scratch\\broken\\Broken.csproj(1,41): error MSB4025: The project file could not be loaded."; + + let paths = vec![ + "C:\\Users\\RUNNER~1\\AppData\\Local\\Temp\\scratch\\broken/Broken.csproj".to_string(), + "C:\\Users\\RUNNER~1\\AppData\\Local\\Temp\\scratch\\ok\\Ok.csproj".to_string(), + ]; + + assert_eq!( + detect_failed_projects(output, &paths), + vec![paths[0].clone()] + ); + assert!(detect_failed_projects("no errors here", &paths).is_empty()); + } + + #[test] + fn detects_failed_projects_without_a_line_and_column() { + // Verbatim shape from SDK 10.0.201 for an unresolvable SDK reference: + // no line/column, and no error code either. Matching only the + // `(line,col):` form left the batch with no offender to exclude, + // so the retry never happened. + let output = "C:\\ws\\bad\\BadSdk.csproj : error : Could not resolve SDK \"Totally.Bogus.Sdk\". Exactly one of the probing messages below indicates why we could not resolve the SDK."; + + let paths = vec![ + "C:\\ws\\bad\\BadSdk.csproj".to_string(), + "C:\\ws\\ok\\Ok.csproj".to_string(), + ]; + + assert_eq!( + detect_failed_projects(output, &paths), + vec![paths[0].clone()] + ); + } + + #[test] + fn normalizes_path_keys() { + assert_eq!( + normalize_path_key("C:\\Abs\\Path\\LibA\\LibA.csproj"), + "c:/abs/path/liba/liba.csproj" + ); + assert_eq!( + normalize_path_key("/home/x/App.csproj"), + "/home/x/app.csproj" + ); + } +} diff --git a/toolchains/dotnet/tests/tier1_test.rs b/toolchains/dotnet/tests/tier1_test.rs new file mode 100644 index 00000000..1357704f --- /dev/null +++ b/toolchains/dotnet/tests/tier1_test.rs @@ -0,0 +1,147 @@ +use moon_pdk_api::*; +use moon_pdk_test_utils::create_empty_moon_sandbox; +use serde_json::json; +use std::path::PathBuf; + +mod dotnet_toolchain_tier1 { + use super::*; + + mod register_toolchain { + use super::*; + + #[tokio::test(flavor = "multi_thread")] + async fn registers_metadata() { + let sandbox = create_empty_moon_sandbox(); + let plugin = sandbox.create_toolchain("dotnet").await; + + let output = plugin + .register_toolchain(RegisterToolchainInput { + id: Id::raw("dotnet"), + }) + .await; + + assert_eq!(output.name, ".NET"); + assert_eq!(output.exe_names, vec!["dotnet".to_string()]); + assert_eq!( + output.lock_file_names, + vec!["packages.lock.json".to_string()] + ); + assert_eq!( + output.manifest_file_names, + vec!["Directory.Packages.props".to_string()] + ); + assert!(output.vendor_dir_name.is_none()); + assert!( + output + .config_file_globs + .contains(&"*.{csproj,fsproj,vbproj}".to_string()) + ); + assert!( + output + .config_file_globs + .contains(&"Directory.Build.targets".to_string()) + ); + assert!( + output + .config_file_globs + .contains(&"{nuget,NuGet}.{config,Config}".to_string()) + ); + assert!( + output + .config_file_globs + .contains(&"packages.*.lock.json".to_string()) + ); + } + } + + mod define_docker_metadata { + use super::*; + + #[tokio::test(flavor = "multi_thread")] + async fn docker_metadata_defaults() { + let sandbox = create_empty_moon_sandbox(); + let plugin = sandbox.create_toolchain("dotnet").await; + + let output = plugin + .define_docker_metadata(DefineDockerMetadataInput { + toolchain_config: json!({}), + ..Default::default() + }) + .await; + + assert_eq!( + output.default_image.unwrap(), + "mcr.microsoft.com/dotnet/sdk:latest" + ); + assert!( + output + .scaffold_globs + .contains(&"**/*.{csproj,fsproj,vbproj}".to_string()) + ); + assert!( + output + .scaffold_globs + .contains(&"**/packages.lock.json".to_string()) + ); + assert!(output.scaffold_globs.contains(&"**/*.targets".to_string())); + assert!( + output + .scaffold_globs + .contains(&"**/packages.*.lock.json".to_string()) + ); + } + } + + mod prune_docker { + use super::*; + + #[tokio::test(flavor = "multi_thread")] + async fn removes_bin_and_obj_dirs() { + let sandbox = create_empty_moon_sandbox(); + sandbox.create_file("app/bin/Debug/x.dll", ""); + sandbox.create_file("app/obj/project.assets.json", ""); + sandbox.create_file("app/keep.cs", ""); + + let plugin = sandbox.create_toolchain("dotnet").await; + + let output = plugin + .prune_docker(PruneDockerInput { + projects: vec![moon_pdk_api::ProjectFragment { + id: Id::raw("app"), + source: "app".into(), + ..Default::default() + }], + root: VirtualPath::Real(sandbox.path().into()), + ..Default::default() + }) + .await; + + assert!(!sandbox.path().join("app/bin").exists()); + assert!(!sandbox.path().join("app/obj").exists()); + assert!(sandbox.path().join("app/keep.cs").exists()); + + assert_eq!( + output.changed_files, + vec![ + PathBuf::from("/workspace/app/bin"), + PathBuf::from("/workspace/app/obj"), + ] + ); + } + + #[tokio::test(flavor = "multi_thread")] + async fn does_nothing_without_bin_obj() { + let sandbox = create_empty_moon_sandbox(); + let plugin = sandbox.create_toolchain("dotnet").await; + + let output = plugin + .prune_docker(PruneDockerInput { + root: VirtualPath::Real(sandbox.path().into()), + ..Default::default() + }) + .await; + + assert!(output.changed_files.is_empty()); + } + } +} diff --git a/toolchains/dotnet/tests/tier2_test.rs b/toolchains/dotnet/tests/tier2_test.rs new file mode 100644 index 00000000..c8f68ae7 --- /dev/null +++ b/toolchains/dotnet/tests/tier2_test.rs @@ -0,0 +1,1394 @@ +//! Most of this file requires a .NET SDK (8+) on `PATH`. +//! +//! `exec_command` is not mocked in the plugin sandbox — warpgate's host function +//! spawns a real process — so `extend_project_graph`, `parse_manifest` and +//! `hash_task_contents` all shell out to `dotnet msbuild` and evaluate the +//! fixtures for real. `-getProperty`/`-getItem` JSON output is what needs SDK 8+. +//! Without one, those tests fail rather than skip. +//! +//! `locate_dependencies_root`, `install_dependencies`, `parse_lock` and +//! `extend_task_command` are pure and need no SDK. + +use moon_config::DependencyScope; +use moon_pdk_api::*; +use moon_pdk_test_utils::{create_empty_moon_sandbox, create_moon_sandbox}; +use serde_json::json; +use std::path::PathBuf; + +mod dotnet_toolchain_tier2 { + use super::*; + + mod locate_dependencies_root { + use super::*; + + #[tokio::test(flavor = "multi_thread")] + async fn finds_solution_root_from_nested_dir() { + let sandbox = create_moon_sandbox("locate"); + let plugin = sandbox.create_toolchain("dotnet").await; + + let output = plugin + .locate_dependencies_root(LocateDependenciesRootInput { + starting_dir: VirtualPath::Real(sandbox.path().join("nested/proj")), + ..Default::default() + }) + .await; + + assert_eq!(output.root.unwrap(), PathBuf::from("/workspace")); + assert!(output.members.is_none()); + } + + #[tokio::test(flavor = "multi_thread")] + async fn falls_back_to_project_file_dir_without_solution() { + let sandbox = create_moon_sandbox("locate-no-sln"); + let plugin = sandbox.create_toolchain("dotnet").await; + + let output = plugin + .locate_dependencies_root(LocateDependenciesRootInput { + starting_dir: VirtualPath::Real(sandbox.path().join("proj")), + ..Default::default() + }) + .await; + + assert_eq!(output.root.unwrap(), PathBuf::from("/workspace/proj")); + } + + #[tokio::test(flavor = "multi_thread")] + async fn finds_slnx_root_from_nested_dir() { + let sandbox = create_empty_moon_sandbox(); + // .slnx is a marker only — content is never parsed. + sandbox.create_file("App.slnx", "\n\n"); + sandbox.create_file( + "nested/proj/Proj.csproj", + "", + ); + + let plugin = sandbox.create_toolchain("dotnet").await; + + let output = plugin + .locate_dependencies_root(LocateDependenciesRootInput { + starting_dir: VirtualPath::Real(sandbox.path().join("nested/proj")), + ..Default::default() + }) + .await; + + assert_eq!(output.root.unwrap(), PathBuf::from("/workspace")); + assert!(output.members.is_none()); + } + + #[tokio::test(flavor = "multi_thread")] + async fn falls_back_to_alternate_lock_file_name() { + let sandbox = create_empty_moon_sandbox(); + sandbox.create_file( + "proj/packages.Proj.lock.json", + r#"{"version": 1, "dependencies": {}}"#, + ); + + let plugin = sandbox.create_toolchain("dotnet").await; + + let output = plugin + .locate_dependencies_root(LocateDependenciesRootInput { + starting_dir: VirtualPath::Real(sandbox.path().join("proj")), + ..Default::default() + }) + .await; + + assert_eq!(output.root.unwrap(), PathBuf::from("/workspace/proj")); + } + + #[tokio::test(flavor = "multi_thread")] + async fn returns_none_when_nothing_found() { + let sandbox = create_empty_moon_sandbox(); + sandbox.create_file("empty/dir/marker.txt", ""); + + let plugin = sandbox.create_toolchain("dotnet").await; + + let output = plugin + .locate_dependencies_root(LocateDependenciesRootInput { + starting_dir: VirtualPath::Real(sandbox.path().join("empty/dir")), + ..Default::default() + }) + .await; + + assert!(output.root.is_none()); + } + } + + mod extend_project_graph { + use super::*; + + fn projects_input() -> ExtendProjectGraphInput { + let mut input = ExtendProjectGraphInput::default(); + input.project_sources.insert(Id::raw("app"), "app".into()); + input.project_sources.insert(Id::raw("lib"), "lib".into()); + input.project_sources.insert(Id::raw("core"), "core".into()); + input + .project_sources + .insert(Id::raw("app-tests"), "app-tests".into()); + input + } + + #[tokio::test(flavor = "multi_thread")] + async fn maps_project_references_to_moon_deps() { + let sandbox = create_moon_sandbox("projects"); + let plugin = sandbox.create_toolchain("dotnet").await; + + let mut input = projects_input(); + input.toolchain_config = json!({ "inferDependencies": true, "inferTasks": false }); + + let output = plugin.extend_project_graph(input).await; + + let app = &output.extended_projects[&Id::raw("app")]; + assert_eq!(app.dependencies.len(), 1); + assert_eq!(app.dependencies[0].id, Id::raw("lib")); + assert_eq!(app.dependencies[0].scope, DependencyScope::Production); + + let lib = &output.extended_projects[&Id::raw("lib")]; + assert_eq!(lib.dependencies[0].id, Id::raw("core")); + + // core has no references, but still contributes its + // AssemblyName-derived alias. + let core = &output.extended_projects[&Id::raw("core")]; + assert!(core.dependencies.is_empty()); + assert_eq!(core.alias.as_deref(), Some("Core")); + + let tests = &output.extended_projects[&Id::raw("app-tests")]; + assert_eq!(tests.dependencies[0].id, Id::raw("app")); + + // One csproj per project, virtual-path form. + assert_eq!(output.input_files.len(), 4); + assert!( + output + .input_files + .contains(&PathBuf::from("/workspace/app/App.csproj")) + ); + } + + #[tokio::test(flavor = "multi_thread")] + async fn respects_infer_dependencies_off() { + let sandbox = create_moon_sandbox("projects"); + let plugin = sandbox.create_toolchain("dotnet").await; + + let mut input = projects_input(); + input.toolchain_config = json!({ "inferDependencies": false, "inferTasks": false }); + + let output = plugin.extend_project_graph(input).await; + + assert!(output.extended_projects.is_empty()); + } + + fn task_ids(project: &ExtendProjectOutput) -> Vec<&str> { + project.tasks.keys().map(|id| id.as_str()).collect() + } + + /// Inferred `test` command for the `mtp` fixture's `suite` project. + async fn mtp_test_command( + sandbox: &moon_pdk_test_utils::MoonWasmSandbox, + ) -> Option { + let plugin = sandbox.create_toolchain("dotnet").await; + + let mut input = ExtendProjectGraphInput::default(); + input + .project_sources + .insert(Id::raw("suite"), "suite".into()); + input.toolchain_config = json!({ "inferDependencies": false }); + + let output = plugin.extend_project_graph(input).await; + + output.extended_projects[&Id::raw("suite")].tasks[&Id::raw("test")] + .command + .clone() + } + + #[tokio::test(flavor = "multi_thread")] + async fn infers_tasks_by_default() { + let sandbox = create_moon_sandbox("projects"); + let plugin = sandbox.create_toolchain("dotnet").await; + + let mut input = projects_input(); + input.toolchain_config = json!({}); + + let output = plugin.extend_project_graph(input).await; + + // app is an Exe -> build + publish + run. + let app = &output.extended_projects[&Id::raw("app")]; + assert_eq!(task_ids(app), vec!["build", "publish", "run"]); + + let build = &app.tasks[&Id::raw("build")]; + assert_eq!( + build.command, + Some(moon_config::PartialTaskArgs::List(vec![ + "dotnet".into(), + "build".into(), + "--no-restore".into(), + "--no-dependencies".into(), + "-c".into(), + "Debug".into(), + ])) + ); + // Outputs came from the real evaluated BaseOutputPath. + assert_eq!( + build.outputs, + Some(vec![moon_config::Output::parse("bin").unwrap()]) + ); + assert!(build.deps.is_some(), "build depends on ^:build"); + + // run is never cached and never runs in CI. + let run_options = app.tasks[&Id::raw("run")].options.as_ref().unwrap(); + assert_eq!( + run_options.cache, + Some(moon_config::TaskOptionCache::Enabled(false)) + ); + + // app-tests references Microsoft.NET.Test.Sdk -> build + test. + let tests = &output.extended_projects[&Id::raw("app-tests")]; + assert_eq!(task_ids(tests), vec!["build", "test"]); + + // Plain classlibs still get a build task. + let core = &output.extended_projects[&Id::raw("core")]; + assert_eq!(task_ids(core), vec!["build"]); + } + + #[tokio::test(flavor = "multi_thread")] + async fn passes_the_project_through_a_flag_for_the_testing_platform() { + // The `mtp` fixture's `global.json` selects Microsoft.Testing.Platform, + // and the project directory holds two project files so the command + // has to name one — the case where the two runners' command lines + // are incompatible. + let sandbox = create_moon_sandbox("mtp"); + + assert_eq!( + mtp_test_command(&sandbox).await, + Some(moon_config::PartialTaskArgs::List(vec![ + "dotnet".into(), + "test".into(), + "--project".into(), + "Suite.Tests.csproj".into(), + "--no-build".into(), + "--no-restore".into(), + "-c".into(), + "Debug".into(), + ])) + ); + } + + #[tokio::test(flavor = "multi_thread")] + async fn passes_the_project_positionally_for_vstest() { + // Same fixture with the runner deselected: classic VSTest mode + // rejects `--project` and requires the positional form. + let sandbox = create_moon_sandbox("mtp"); + sandbox.create_file("global.json", "{}"); + + assert_eq!( + mtp_test_command(&sandbox).await, + Some(moon_config::PartialTaskArgs::List(vec![ + "dotnet".into(), + "test".into(), + "Suite.Tests.csproj".into(), + "--no-build".into(), + "--no-restore".into(), + "-c".into(), + "Debug".into(), + ])) + ); + } + + #[tokio::test(flavor = "multi_thread")] + async fn infers_only_listed_tasks() { + let sandbox = create_moon_sandbox("projects"); + let plugin = sandbox.create_toolchain("dotnet").await; + + let mut input = projects_input(); + input.toolchain_config = json!({ "inferDependencies": false, "inferTasks": ["test"] }); + + let output = plugin.extend_project_graph(input).await; + + // Only app-tests qualifies for a test task; nothing else + // contributes anything. + let tests = &output.extended_projects[&Id::raw("app-tests")]; + assert_eq!(task_ids(tests), vec!["test"]); + + // The others still appear, but only to contribute their + // AssemblyName alias — no tasks, and no deps with inference off. + for id in ["app", "core", "lib"] { + let project = &output.extended_projects[&Id::raw(id)]; + + assert!(project.tasks.is_empty(), "{id} tasks"); + assert!(project.dependencies.is_empty(), "{id} deps"); + assert!(project.alias.is_some(), "{id} alias"); + } + } + + #[tokio::test(flavor = "multi_thread")] + async fn inference_yields_to_inherited_task_files() { + let sandbox = create_moon_sandbox("projects"); + + // Applies to dotnet projects: suppresses inferred `build`. + sandbox.create_file( + ".moon/tasks/dotnet.yml", + "inheritedBy:\n toolchains: ['dotnet']\ntasks:\n build:\n command: 'dotnet build'\n", + ); + // Unscoped: assumed to apply -> suppresses inferred `publish`. + sandbox.create_file( + ".moon/tasks.yml", + "tasks:\n publish:\n command: 'echo deploy'\n", + ); + // Explicitly scoped to another toolchain: must NOT suppress `run`. + sandbox.create_file( + ".moon/tasks/node.yml", + "inheritedBy:\n toolchains: ['javascript']\ntasks:\n run:\n command: 'node server.js'\n", + ); + + let plugin = sandbox.create_toolchain("dotnet").await; + + let mut input = projects_input(); + input.toolchain_config = json!({ "inferDependencies": false }); + + let output = plugin.extend_project_graph(input).await; + + let app = &output.extended_projects[&Id::raw("app")]; + assert_eq!(task_ids(app), vec!["run"]); + + let tests = &output.extended_projects[&Id::raw("app-tests")]; + assert_eq!(task_ids(tests), vec!["test"]); + } + + #[tokio::test(flavor = "multi_thread")] + async fn infers_dependencies_across_languages() { + let sandbox = create_moon_sandbox("mixed-lang"); + let plugin = sandbox.create_toolchain("dotnet").await; + + let mut input = ExtendProjectGraphInput::default(); + input.project_sources.insert(Id::raw("app"), "app".into()); + input.project_sources.insert(Id::raw("lib"), "lib".into()); + input.project_sources.insert(Id::raw("core"), "core".into()); + input.toolchain_config = json!({ "inferDependencies": true }); + + let output = plugin.extend_project_graph(input).await; + + // C# -> F# -> VB project references all resolve; MSBuild + // evaluation is language-agnostic. + let app = &output.extended_projects[&Id::raw("app")]; + assert_eq!(app.dependencies[0].id, Id::raw("lib")); + + let lib = &output.extended_projects[&Id::raw("lib")]; + assert_eq!(lib.dependencies[0].id, Id::raw("core")); + + assert!( + output + .input_files + .contains(&PathBuf::from("/workspace/lib/Lib.fsproj")) + ); + assert!( + output + .input_files + .contains(&PathBuf::from("/workspace/core/Core.vbproj")) + ); + } + + #[tokio::test(flavor = "multi_thread")] + async fn emits_assembly_name_as_alias() { + let sandbox = create_moon_sandbox("matrix"); + let plugin = sandbox.create_toolchain("dotnet").await; + + let mut input = ExtendProjectGraphInput::default(); + input + .project_sources + .insert(Id::raw("deep"), "nested/deep".into()); + input.toolchain_config = json!({ "inferDependencies": true }); + + let output = plugin.extend_project_graph(input).await; + + // Explicit beats the file-name default. + let deep = &output.extended_projects[&Id::raw("deep")]; + assert_eq!(deep.alias.as_deref(), Some("MyCompany.Deep")); + } + + #[tokio::test(flavor = "multi_thread")] + async fn condition_gated_project_references_resolve() { + let sandbox = create_moon_sandbox("matrix"); + let plugin = sandbox.create_toolchain("dotnet").await; + + let mut input = ExtendProjectGraphInput::default(); + input + .project_sources + .insert(Id::raw("deep"), "nested/deep".into()); + input.project_sources.insert(Id::raw("cond"), "cond".into()); + input.toolchain_config = json!({ "inferDependencies": true }); + + let output = plugin.extend_project_graph(input).await; + + // The ProjectReference is gated on '$(EnableDeepRef)' == '1', + // set in the project itself — real evaluation resolves it. + let cond = &output.extended_projects[&Id::raw("cond")]; + assert_eq!(cond.dependencies[0].id, Id::raw("deep")); + } + + #[tokio::test(flavor = "multi_thread")] + async fn unsatisfiable_global_json_pin_fails_with_guidance() { + let sandbox = create_moon_sandbox("projects"); + // No such SDK exists, so the dotnet host refuses to run MSBuild + // at all — every project would fail identically. + sandbox.create_file( + "global.json", + r#"{"sdk":{"version":"99.0.100","rollForward":"disable"}}"#, + ); + + let plugin = sandbox.create_toolchain("dotnet").await; + + let mut input = projects_input(); + input.toolchain_config = json!({ "inferDependencies": true }); + input.context = plugin.create_context(); + + // The wrapper unwraps, so call through the plugin to inspect the + // error itself. + let error = plugin + .plugin + .call_func_with::<_, _, ExtendProjectGraphOutput>("extend_project_graph", input) + .await + .expect_err("an unsatisfiable SDK pin must fail the graph build") + .to_string(); + + // Names the pin, where it came from, and the ways out — instead + // of one cryptic host dump per project and an empty graph. + assert!(error.contains("99.0.100"), "{error}"); + assert!(error.contains("global.json"), "{error}"); + assert!(error.contains("version"), "{error}"); + assert!(error.contains("dotnetRoot"), "{error}"); + } + + #[tokio::test(flavor = "multi_thread")] + async fn unsatisfiable_pin_degrades_when_moon_installs_the_sdk() { + let sandbox = create_moon_sandbox("projects"); + sandbox.create_file( + "global.json", + r#"{"sdk":{"version":"99.0.100","rollForward":"disable"}}"#, + ); + + // Same unsatisfiable pin, but moon has been told to install an SDK. + // The project graph is built before the action pipeline runs, so + // failing here would deadlock the bootstrap this setting exists for. + sandbox.create_file(".moon/toolchains.yml", "dotnet:\n version: '8.0'\n"); + + let plugin = sandbox.create_toolchain("dotnet").await; + + let mut input = projects_input(); + input.toolchain_config = json!({ "inferDependencies": true }); + input.context = plugin.create_context(); + + let output = plugin + .plugin + .call_func_with::<_, _, ExtendProjectGraphOutput>("extend_project_graph", input) + .await + .expect("a pending SDK install must not fail the graph build"); + + assert!(output.extended_projects.is_empty()); + } + + #[tokio::test(flavor = "multi_thread")] + async fn broken_project_does_not_abort_graph() { + let sandbox = create_moon_sandbox("projects"); + sandbox.create_file( + "core/Core.csproj", + " same key; different content -> different key. + let repeat = plugin + .setup_environment(SetupEnvironmentInput { + root: VirtualPath::Real(sandbox.path().into()), + toolchain_config: json!({}), + ..Default::default() + }) + .await; + + assert_eq!(repeat.commands[0].cache.as_deref(), Some(cache_key)); + + sandbox.create_file( + ".config/dotnet-tools.json", + r#"{"version": 1, "isRoot": true, "tools": {"dotnetsay": {"version": "2.1.7", "commands": ["dotnetsay"]}}}"#, + ); + + let edited = plugin + .setup_environment(SetupEnvironmentInput { + root: VirtualPath::Real(sandbox.path().into()), + toolchain_config: json!({}), + ..Default::default() + }) + .await; + + assert_ne!(edited.commands[0].cache.as_deref(), Some(cache_key)); + } + + #[tokio::test(flavor = "multi_thread")] + async fn finds_tool_manifest_above_the_dependencies_root() { + let sandbox = create_moon_sandbox("projects"); + // Tool manifests conventionally live at the repository root, but + // any project directory with a lock file is its own dependencies + // root — so the lookup walks upward like the dotnet CLI does. + sandbox.create_file( + ".config/dotnet-tools.json", + r#"{"version": 1, "isRoot": true, "tools": {}}"#, + ); + + let plugin = sandbox.create_toolchain("dotnet").await; + + let output = plugin + .setup_environment(SetupEnvironmentInput { + root: VirtualPath::Real(sandbox.path().join("app")), + toolchain_config: json!({}), + ..Default::default() + }) + .await; + + assert_eq!(output.commands.len(), 1); + assert_eq!( + output.commands[0].command.args, + vec!["tool".to_string(), "restore".to_string()] + ); + } + } + + mod hash_task_contents { + use super::*; + + fn fragment(id: &str, source: &str) -> moon_pdk_api::ProjectFragment { + moon_pdk_api::ProjectFragment { + id: Id::raw(id), + source: source.into(), + toolchains: vec![Id::raw("dotnet")], + ..Default::default() + } + } + + #[tokio::test(flavor = "multi_thread")] + async fn lockfile_branch_includes_raw_lock_text() { + let sandbox = create_moon_sandbox("locked"); + let plugin = sandbox.create_toolchain("dotnet").await; + + let output = plugin + .hash_task_contents(HashTaskContentsInput { + project: fragment("proj", "proj"), + toolchain_config: json!({}), + ..Default::default() + }) + .await; + + assert_eq!(output.contents.len(), 1); + let lockfiles = output.contents[0]["lockfiles"].as_object().unwrap(); + let lock_text = lockfiles["/workspace/proj/packages.lock.json"] + .as_str() + .unwrap(); + assert!(lock_text.contains("Newtonsoft.Json")); + assert!(lock_text.contains("contentHash")); + } + + #[tokio::test(flavor = "multi_thread")] + async fn lockfile_branch_still_hashes_config_files() { + let sandbox = create_moon_sandbox("locked"); + // Even with the package set pinned by the lock file, props/targets + // change build behavior and must contribute to the hash. + sandbox.create_file( + "Directory.Build.props", + "12", + ); + + let plugin = sandbox.create_toolchain("dotnet").await; + + let output = plugin + .hash_task_contents(HashTaskContentsInput { + project: fragment("proj", "proj"), + toolchain_config: json!({}), + ..Default::default() + }) + .await; + + let contents = &output.contents[0]; + assert!(contents["lockfiles"].is_object()); + let configs = contents["configs"].as_object().unwrap(); + assert!( + configs["/workspace/Directory.Build.props"] + .as_str() + .unwrap() + .contains("LangVersion") + ); + } + + #[tokio::test(flavor = "multi_thread")] + async fn alternate_lock_file_name_takes_lock_branch() { + let sandbox = create_moon_sandbox("projects"); + // `packages..lock.json` via NuGetLockFilePath. + sandbox.create_file( + "app/packages.App.lock.json", + r#"{"version": 1, "dependencies": {}}"#, + ); + + let plugin = sandbox.create_toolchain("dotnet").await; + + let output = plugin + .hash_task_contents(HashTaskContentsInput { + project: fragment("app", "app"), + toolchain_config: json!({}), + ..Default::default() + }) + .await; + + let contents = &output.contents[0]; + let lockfiles = contents["lockfiles"].as_object().unwrap(); + assert!(lockfiles.contains_key("/workspace/app/packages.App.lock.json")); + // Lock branch: no MSBuild evaluation happens. + assert!(contents.get("packages").is_none()); + } + + #[tokio::test(flavor = "multi_thread")] + async fn hashes_all_config_file_kinds() { + let sandbox = create_moon_sandbox("projects"); + // Valid-but-harmless contents: MSBuild auto-imports + // Directory.Build.targets and auto-applies Directory.Build.rsp, + // so garbage would break evaluation of the fixture projects. + sandbox.create_file("core/Directory.Build.targets", ""); + sandbox.create_file("Directory.Build.rsp", ""); + sandbox.create_file("NuGet.Config", ""); + sandbox.create_file("global.json", "{}"); + + let plugin = sandbox.create_toolchain("dotnet").await; + + let output = plugin + .hash_task_contents(HashTaskContentsInput { + project: fragment("core", "core"), + toolchain_config: json!({}), + ..Default::default() + }) + .await; + + let configs = output.contents[0]["configs"].as_object().unwrap(); + assert!(configs.contains_key("/workspace/core/Directory.Build.targets")); + assert!(configs.contains_key("/workspace/Directory.Build.rsp")); + // Actual (non-lowercase) file name is preserved in the key. + assert!(configs.contains_key("/workspace/NuGet.Config")); + assert!(configs.contains_key("/workspace/global.json")); + } + + #[tokio::test(flavor = "multi_thread")] + async fn evaluated_packages_branch_without_lockfile() { + let sandbox = create_moon_sandbox("projects"); + sandbox.create_file( + "Directory.Build.props", + "latest", + ); + + let plugin = sandbox.create_toolchain("dotnet").await; + + let output = plugin + .hash_task_contents(HashTaskContentsInput { + project: fragment("app-tests", "app-tests"), + toolchain_config: json!({}), + ..Default::default() + }) + .await; + + assert_eq!(output.contents.len(), 1); + let contents = &output.contents[0]; + + assert_eq!(contents["packages"]["xunit"].as_str().unwrap(), "2.8.0"); + assert_eq!( + contents["packages"]["Microsoft.NET.Test.Sdk"] + .as_str() + .unwrap(), + "17.10.0" + ); + + let configs = contents["configs"].as_object().unwrap(); + assert_eq!(configs.len(), 1); + assert!( + configs + .values() + .next() + .unwrap() + .as_str() + .unwrap() + .contains("LangVersion") + ); + } + + #[tokio::test(flavor = "multi_thread")] + async fn props_inheritance_chain_hashes_every_level() { + let sandbox = create_moon_sandbox("matrix"); + let plugin = sandbox.create_toolchain("dotnet").await; + + let output = plugin + .hash_task_contents(HashTaskContentsInput { + project: fragment("deep", "nested/deep"), + toolchain_config: json!({}), + ..Default::default() + }) + .await; + + let contents = &output.contents[0]; + let configs = contents["configs"].as_object().unwrap(); + + // Every props file from the project dir up to the workspace root + // is content-hashed, not just the nearest one. + assert!(configs.contains_key("/workspace/nested/Directory.Build.props")); + assert!(configs.contains_key("/workspace/Directory.Build.props")); + + // The nested props chains to the root props via + // GetPathOfFileAbove, so packages from both levels evaluate in. + let packages = contents["packages"].as_object().unwrap(); + assert_eq!(packages["NestedPkg"].as_str().unwrap(), "2.0.0"); + assert_eq!(packages["RootPkg"].as_str().unwrap(), "1.0.0"); + } + + #[tokio::test(flavor = "multi_thread")] + async fn multi_targeted_project_hashes_the_outer_build() { + let sandbox = create_moon_sandbox("matrix"); + let plugin = sandbox.create_toolchain("dotnet").await; + + let output = plugin + .hash_task_contents(HashTaskContentsInput { + project: fragment("multi", "multi"), + toolchain_config: json!({}), + ..Default::default() + }) + .await; + + let packages = output.contents[0]["packages"].as_object().unwrap(); + + // Documented scope cut: evaluation is the outer (cross-targeting) + // build, where TargetFramework is empty — so per-TFM conditional + // packages are invisible. The root props package still resolves, + // proving the project itself evaluated. + assert!(packages.contains_key("RootPkg")); + assert!(!packages.contains_key("Net8OnlyPkg")); + } + + #[tokio::test(flavor = "multi_thread")] + async fn condition_gated_packages_resolve_by_evaluation() { + let sandbox = create_moon_sandbox("matrix"); + let plugin = sandbox.create_toolchain("dotnet").await; + + let output = plugin + .hash_task_contents(HashTaskContentsInput { + project: fragment("cond", "cond"), + toolchain_config: json!({}), + ..Default::default() + }) + .await; + + let packages = output.contents[0]["packages"].as_object().unwrap(); + + // Conditions are resolved by MSBuild, so a true condition + // contributes and a false one does not. + assert_eq!(packages["ExtraPkg"].as_str().unwrap(), "3.0.0"); + assert!(!packages.contains_key("NeverPkg")); + } + + #[tokio::test(flavor = "multi_thread")] + async fn central_package_management_hashes_via_props() { + let sandbox = create_moon_sandbox("cpm"); + let plugin = sandbox.create_toolchain("dotnet").await; + + let output = plugin + .hash_task_contents(HashTaskContentsInput { + project: fragment("proj", "proj"), + toolchain_config: json!({}), + ..Default::default() + }) + .await; + + let contents = &output.contents[0]; + + // CPM applies versions during restore, not evaluation, so the + // versionless PackageReference surfaces as "*" — the pinned + // version reaches the hash through the Directory.Packages.props + // content below, which is what keeps caching correct. + assert_eq!( + contents["packages"]["Newtonsoft.Json"].as_str().unwrap(), + "*" + ); + + let configs = contents["configs"].as_object().unwrap(); + assert!( + configs["/workspace/Directory.Packages.props"] + .as_str() + .unwrap() + .contains("13.0.3") + ); + } + + #[tokio::test(flavor = "multi_thread")] + async fn reuses_the_package_set_from_the_batched_graph_evaluation() { + let sandbox = create_moon_sandbox("projects"); + let plugin = sandbox.create_toolchain("dotnet").await; + + // Build the graph first: that is where the single batched + // evaluation happens, and it primes the on-disk package sets. + let mut graph_input = ExtendProjectGraphInput::default(); + graph_input + .project_sources + .insert(Id::raw("app-tests"), "app-tests".into()); + graph_input.toolchain_config = json!({ "inferTasks": false }); + + plugin.extend_project_graph(graph_input).await; + + let cache_file = sandbox + .path() + .join(".moon/cache/dotnet-toolchain/eval/app-tests.json"); + + assert!( + cache_file.exists(), + "the graph build must persist the evaluated package set" + ); + + let mut entry: serde_json::Value = + serde_json::from_str(&std::fs::read_to_string(&cache_file).unwrap()).unwrap(); + assert_eq!(entry["packages"]["xunit"].as_str().unwrap(), "2.8.0"); + + // Swap in a package MSBuild could never report, keeping the + // digest: if hashing returns it, the entry was reused instead of + // re-evaluating. + let digest = entry["digest"].as_str().unwrap().to_string(); + entry["packages"] = json!({ "SentinelOnlyInCache": "1.2.3" }); + std::fs::write(&cache_file, entry.to_string()).unwrap(); + + let output = plugin + .hash_task_contents(HashTaskContentsInput { + project: fragment("app-tests", "app-tests"), + toolchain_config: json!({}), + ..Default::default() + }) + .await; + + assert_eq!( + output.contents[0]["packages"]["SentinelOnlyInCache"] + .as_str() + .unwrap(), + "1.2.3", + "task hashing must reuse the primed package set" + ); + + // Editing the project file must invalidate the entry rather than + // serving that stale set. A fresh plugin instance avoids the + // in-instance memo from the call above. + let csproj = sandbox.path().join("app-tests/App.Tests.csproj"); + let edited = std::fs::read_to_string(&csproj) + .unwrap() + .replace("2.8.0", "2.9.0"); + std::fs::write(&csproj, edited).unwrap(); + + let plugin = sandbox.create_toolchain("dotnet").await; + + let output = plugin + .hash_task_contents(HashTaskContentsInput { + project: fragment("app-tests", "app-tests"), + toolchain_config: json!({}), + ..Default::default() + }) + .await; + + let packages = &output.contents[0]["packages"]; + assert!( + packages.get("SentinelOnlyInCache").is_none(), + "a project-file edit must invalidate the cached package set" + ); + assert_eq!(packages["xunit"].as_str().unwrap(), "2.9.0"); + + // ...and the refreshed entry replaces the stale one on disk. + let refreshed: serde_json::Value = + serde_json::from_str(&std::fs::read_to_string(&cache_file).unwrap()).unwrap(); + assert_ne!(refreshed["digest"].as_str().unwrap(), digest); + assert_eq!(refreshed["packages"]["xunit"].as_str().unwrap(), "2.9.0"); + } + + #[tokio::test(flavor = "multi_thread")] + async fn does_not_cache_a_package_set_it_could_not_fully_evaluate() { + let sandbox = create_moon_sandbox("unevaluatable"); + let plugin = sandbox.create_toolchain("dotnet").await; + + let mut graph_input = ExtendProjectGraphInput::default(); + graph_input + .project_sources + .insert(Id::raw("proj"), "proj".into()); + graph_input.toolchain_config = json!({ "inferTasks": false }); + + plugin.extend_project_graph(graph_input).await; + + // An unloadable project yields no package set. Persisting the empty + // one would validate forever under its digest, and since that set + // is the only hash signal without a lock file, package changes + // would stop invalidating task hashes entirely. + assert!( + !sandbox + .path() + .join(".moon/cache/dotnet-toolchain/eval/proj.json") + .exists(), + "an incomplete package set must not reach the on-disk cache" + ); + } + + #[tokio::test(flavor = "multi_thread")] + async fn skips_projects_without_dotnet_toolchain() { + let sandbox = create_moon_sandbox("projects"); + let plugin = sandbox.create_toolchain("dotnet").await; + + let mut project = fragment("app", "app"); + project.toolchains = vec![]; + + let output = plugin + .hash_task_contents(HashTaskContentsInput { + project, + toolchain_config: json!({}), + ..Default::default() + }) + .await; + + assert!(output.contents.is_empty()); + } + } + + // `get_env_var` in the plugin reads the *real* host process environment, so + // an ambient `DOTNET_ROOT` — `actions/setup-dotnet` exports one on every CI + // runner — takes precedence and returns before `resolve_dotnet_root` ever + // consults the home-dir fallback or `global.json`. Assertions here must + // therefore hold in both environments; `assert_ne!` against the sandbox's + // own `.home/.dotnet` does, because that path is never the ambient value. + // Removing the variable instead would need `unsafe { env::remove_var }`, + // which is unsound in these multi-threaded tests. + mod extend_task_command { + use super::*; + + #[tokio::test(flavor = "multi_thread")] + async fn injects_explicit_dotnet_root() { + let sandbox = create_empty_moon_sandbox(); + let plugin = sandbox.create_toolchain("dotnet").await; + + let output = plugin + .extend_task_command(ExtendTaskCommandInput { + toolchain_config: json!({ "dotnetRoot": "/custom/dotnet" }), + ..Default::default() + }) + .await; + + assert_eq!(output.env.get("DOTNET_ROOT").unwrap(), "/custom/dotnet"); + assert_eq!( + output.paths, + vec![std::path::PathBuf::from("/custom/dotnet")] + ); + + // DOTNET_ROOT is the only variable injected; vendor environment + // variables belong in a task's own `env`. + assert_eq!(output.env.len(), 1); + } + + #[tokio::test(flavor = "multi_thread")] + async fn falls_back_to_home_dotnet_when_sdk_layout_present() { + let sandbox = create_empty_moon_sandbox(); + + // A real SDK layout has the dotnet host executable at the root. + let exe = if cfg!(windows) { + "dotnet.exe" + } else { + "dotnet" + }; + sandbox.create_file(format!(".home/.dotnet/{exe}").as_str(), ""); + + let plugin = sandbox.create_toolchain("dotnet").await; + + let output = plugin + .extend_task_command(ExtendTaskCommandInput { + toolchain_config: json!({}), + ..Default::default() + }) + .await; + + let root = output.env.get("DOTNET_ROOT").expect("DOTNET_ROOT not set"); + + // Positive assertion, so it can only check the fallback value when + // no ambient DOTNET_ROOT pre-empts it — see the note on this module. + match std::env::var("DOTNET_ROOT") { + Ok(ambient) if !ambient.is_empty() => assert_eq!(root, &ambient), + _ => assert!(root.contains(".dotnet")), + } + + assert_eq!(output.paths.len(), 1); + } + + #[tokio::test(flavor = "multi_thread")] + async fn skips_home_fallback_that_cannot_satisfy_global_json() { + let sandbox = create_moon_sandbox("projects"); + + // A leftover ~/.dotnet holding only SDK 8 — the exact shape that + // made every task fail against a 10.x pin in a real repo. + let exe = if cfg!(windows) { + "dotnet.exe" + } else { + "dotnet" + }; + sandbox.create_file(format!(".home/.dotnet/{exe}").as_str(), ""); + sandbox.create_file(".home/.dotnet/sdk/8.0.423/marker", ""); + sandbox.create_file( + "global.json", + r#"{"sdk":{"version":"10.0.301","rollForward":"latestMajor"}}"#, + ); + + let plugin = sandbox.create_toolchain("dotnet").await; + + let output = plugin + .extend_task_command(ExtendTaskCommandInput { + project: moon_pdk_api::ProjectFragment { + id: Id::raw("app"), + source: "app".into(), + toolchains: vec![Id::raw("dotnet")], + ..Default::default() + }, + toolchain_config: json!({}), + ..Default::default() + }) + .await; + + // Holds whether or not an ambient DOTNET_ROOT is set: with one, it + // wins and is never the sandbox home; without one, the guard leaves + // DOTNET_ROOT unset. Either way the unsatisfying ~/.dotnet must not + // be what we inject. The satisfaction rules themselves are + // unit-tested in `global_json`. + assert_ne!( + output.env.get("DOTNET_ROOT").map(String::as_str), + sandbox.path().join(".home/.dotnet").to_str(), + "an SDK-8-only ~/.dotnet must not be injected for a 10.x pin" + ); + } + + #[tokio::test(flavor = "multi_thread")] + async fn uses_home_fallback_that_satisfies_global_json() { + let sandbox = create_moon_sandbox("projects"); + + let exe = if cfg!(windows) { + "dotnet.exe" + } else { + "dotnet" + }; + sandbox.create_file(format!(".home/.dotnet/{exe}").as_str(), ""); + sandbox.create_file(".home/.dotnet/sdk/10.0.301/marker", ""); + sandbox.create_file( + "global.json", + r#"{"sdk":{"version":"10.0.301","rollForward":"latestMajor"}}"#, + ); + + let plugin = sandbox.create_toolchain("dotnet").await; + + let output = plugin + .extend_task_command(ExtendTaskCommandInput { + project: moon_pdk_api::ProjectFragment { + id: Id::raw("app"), + source: "app".into(), + toolchains: vec![Id::raw("dotnet")], + ..Default::default() + }, + toolchain_config: json!({}), + ..Default::default() + }) + .await; + + let root = output.env.get("DOTNET_ROOT").expect("DOTNET_ROOT not set"); + + match std::env::var("DOTNET_ROOT") { + Ok(ambient) if !ambient.is_empty() => assert_eq!(root, &ambient), + _ => assert!(root.contains(".dotnet")), + } + } + + #[tokio::test(flavor = "multi_thread")] + async fn no_injection_without_any_dotnet_root() { + let sandbox = create_empty_moon_sandbox(); + + // `~/.dotnet` existing as a mere cache dir (no dotnet executable) + // must NOT be treated as a DOTNET_ROOT. + sandbox.create_file(".home/.dotnet/sdk/8.0.404/marker", ""); + + let plugin = sandbox.create_toolchain("dotnet").await; + + let output = plugin + .extend_task_command(ExtendTaskCommandInput { + toolchain_config: json!({}), + ..Default::default() + }) + .await; + + // Unconditional: an ambient DOTNET_ROOT is never the sandbox home, + // so this asserts the cache dir was rejected in both environments. + // The previous `if env::var(..).is_err()` guard meant this test + // asserted nothing at all on CI. + assert_ne!( + output.env.get("DOTNET_ROOT").map(String::as_str), + sandbox.path().join(".home/.dotnet").to_str(), + "a ~/.dotnet with no dotnet executable is a cache dir, not a DOTNET_ROOT" + ); + } + } +} diff --git a/toolchains/dotnet/tests/tier3_test.rs b/toolchains/dotnet/tests/tier3_test.rs new file mode 100644 index 00000000..082a02ed --- /dev/null +++ b/toolchains/dotnet/tests/tier3_test.rs @@ -0,0 +1,98 @@ +use moon_config::UnresolvedVersionSpec; +use moon_pdk_api::*; +use moon_pdk_test_utils::create_empty_moon_sandbox; +use serde_json::json; + +mod dotnet_toolchain_tier3 { + use super::*; + + mod setup_toolchain { + use super::*; + + #[tokio::test(flavor = "multi_thread")] + async fn no_configured_version_is_a_noop() { + let sandbox = create_empty_moon_sandbox(); + let plugin = sandbox.create_toolchain("dotnet").await; + + let output = plugin + .setup_toolchain(SetupToolchainInput { + configured_version: None, + toolchain_config: json!({}), + ..Default::default() + }) + .await; + + assert!(!output.installed); + assert!(output.operations.is_empty()); + assert!(output.changed_files.is_empty()); + } + + #[tokio::test(flavor = "multi_thread")] + async fn exact_version_already_installed_skips_without_network() { + let sandbox = create_empty_moon_sandbox(); + // A pre-existing SDK layout at the default `~/.dotnet` root. + // Anything past the short-circuit would hit the network and + // fail the test, so completing cleanly proves the skip. + sandbox.create_file(".home/.dotnet/sdk/8.0.404/marker", ""); + + let plugin = sandbox.create_toolchain("dotnet").await; + + let output = plugin + .setup_toolchain(SetupToolchainInput { + configured_version: Some(UnresolvedVersionSpec::parse("8.0.404").unwrap()), + toolchain_config: json!({}), + ..Default::default() + }) + .await; + + assert!(!output.installed); + assert!(output.operations.is_empty()); + } + + #[tokio::test(flavor = "multi_thread")] + #[should_panic(expected = "Unsupported .NET version")] + async fn unsupported_version_spec_errors() { + let sandbox = create_empty_moon_sandbox(); + let plugin = sandbox.create_toolchain("dotnet").await; + + plugin + .setup_toolchain(SetupToolchainInput { + configured_version: Some(UnresolvedVersionSpec::parse("canary").unwrap()), + toolchain_config: json!({}), + ..Default::default() + }) + .await; + } + + // Downloads and runs the real dotnet-install script, fetching a full + // ~200 MB SDK, so it stays out of the default run. On demand: + // cargo nextest run -p dotnet_toolchain --no-default-features \ + // --run-ignored=only full_sdk_install + #[tokio::test(flavor = "multi_thread")] + #[ignore = "downloads a full .NET SDK from the network"] + async fn full_sdk_install() { + let sandbox = create_empty_moon_sandbox(); + let plugin = sandbox.create_toolchain("dotnet").await; + + let root = sandbox.path().join(".home/.dotnet"); + + let output = plugin + .setup_toolchain(SetupToolchainInput { + configured_version: Some(UnresolvedVersionSpec::parse("8.0").unwrap()), + toolchain_config: json!({}), + ..Default::default() + }) + .await; + + assert_eq!(output.operations.len(), 1); + + let exe = if cfg!(windows) { + "dotnet.exe" + } else { + "dotnet" + }; + assert!(root.join(exe).exists()); + assert!(root.join("sdk").exists()); + } + } +} From 8e624aa0d683e6031f9bfe2aa68f29d7a0c9f75e Mon Sep 17 00:00:00 2001 From: Wouter Date: Mon, 27 Jul 2026 23:09:16 +0200 Subject: [PATCH 78/78] feat(dotnet): add msbuildProperties for evaluation-time properties Evaluation runs with the SDK's default property values, so a reference or package behind a condition lands in the graph even in a workspace whose real builds never enable it. `msbuildProperties` sets MSBuild global properties for evaluation only, applied to both the batched traversal and the per-project fallback. The properties form part of the evaluation cache digest, because a conditional PackageReference resolves differently under different values and a cached package set must not be served across configurations. Inferred task commands and `dotnet restore` do not receive them, so `moon run` builds stay exactly what the project defines. --- toolchains/dotnet/CHANGELOG.md | 4 ++ toolchains/dotnet/src/config.rs | 54 ++++++++++++++++++++ toolchains/dotnet/src/eval_cache.rs | 63 +++++++++++++++++++++--- toolchains/dotnet/src/msbuild.rs | 23 ++++++++- toolchains/dotnet/src/project_graph.rs | 1 + toolchains/dotnet/src/tier2.rs | 16 +++--- toolchains/dotnet/src/tier2_env.rs | 1 + toolchains/dotnet/tests/msbuild_test.rs | 20 ++++++++ toolchains/dotnet/tests/tier2_test.rs | 65 +++++++++++++++++++++++++ 9 files changed, 233 insertions(+), 14 deletions(-) diff --git a/toolchains/dotnet/CHANGELOG.md b/toolchains/dotnet/CHANGELOG.md index 704f6dbc..78bc553d 100644 --- a/toolchains/dotnet/CHANGELOG.md +++ b/toolchains/dotnet/CHANGELOG.md @@ -21,3 +21,7 @@ `$(SolutionDir)`, conditional references and Central Package Management all resolve the way the SDK resolves them. Every project in the workspace is evaluated in a single batched invocation, and the results are cached on disk for task hashing to reuse. +- Evaluation accepts additional MSBuild properties through `msbuildProperties`, for + workspaces where a conditional reference or package should resolve the way the code is + actually built rather than under the SDK defaults. They apply to evaluation only, form + part of the evaluation cache key, and are never passed to inferred task commands. diff --git a/toolchains/dotnet/src/config.rs b/toolchains/dotnet/src/config.rs index 754ccbe2..c900188c 100644 --- a/toolchains/dotnet/src/config.rs +++ b/toolchains/dotnet/src/config.rs @@ -1,5 +1,6 @@ use moon_pdk_api::config_struct; use schematic::{Config, Schematic}; +use std::collections::BTreeMap; /// The task names the plugin can infer. pub const INFERABLE_TASKS: &[&str] = &["build", "test", "run", "publish"]; @@ -121,6 +122,40 @@ config_struct!( /// (a leftover install there is otherwise skipped in favour of the /// `dotnet` on `PATH`). Set explicitly, it is never second-guessed. pub dotnet_root: Option, + + /// Additional MSBuild properties applied to every evaluation behind + /// dependency and task inference, passed as `-p:NAME=VALUE`. + /// + /// Use this when the graph must be evaluated the way the code is + /// actually deployed. The motivating case: conditional, codegen-only + /// `ProjectReference`s (`ReferenceOutputAssembly=false`, gated on a + /// property like `Condition="'$(SkipApiClientGen)' != 'true'"`) that a + /// production/Docker build disables. Without the property, evaluation + /// includes build-ordering edges the deployed build never compiles — + /// over-attributing affected projects, and doing so + /// platform-dependently when the condition involves globs. + /// + /// ```yaml + /// dotnet: + /// msbuildProperties: + /// SkipApiClientGen: 'true' + /// ``` + /// + /// These are evaluation-time only, and the boundary is deliberate: + /// neither inferred task commands nor `dotnet restore` pass them, so + /// `moon run` builds stay exactly what the project defines. Two + /// consequences worth knowing: + /// + /// - Keep these consistent with how the code is actually built. Setting + /// a property here that your real build does not set makes the graph + /// describe a build nobody runs. Inferred `build` tasks pass + /// `--no-dependencies`, so moon is the only thing ordering + /// dependencies: dropping an edge here drops that ordering too. + /// - A `PackageReference` gated on one of these properties is resolved + /// for hashing but not for restore, so the recorded package set can + /// differ from what restore installs. Harmless when the properties + /// match the build; a reason not to gate packages on them otherwise. + pub msbuild_properties: BTreeMap, } ); @@ -137,6 +172,7 @@ mod tests { assert!(json.contains("inferTasks")); assert!(json.contains("restoreArgs")); assert!(json.contains("dotnetRoot")); + assert!(json.contains("msbuildProperties")); // `inferTasks` must stay a `bool | string[]` union. The derive produces // this from the untagged enum; asserting the shape means a change to the @@ -158,6 +194,24 @@ mod tests { assert!(config.infer_tasks.any_enabled()); assert!(config.restore_args.is_empty()); assert!(config.dotnet_root.is_none()); + assert!(config.msbuild_properties.is_empty()); + } + + #[test] + fn msbuild_properties_deserialize_from_camel_case() { + let config: DotnetToolchainConfig = serde_json::from_value(serde_json::json!({ + "msbuildProperties": { "SkipApiClientGen": "true", "Answer": "42" } + })) + .unwrap(); + + assert_eq!( + config.msbuild_properties.get("SkipApiClientGen"), + Some(&"true".to_owned()) + ); + assert_eq!( + config.msbuild_properties.get("Answer"), + Some(&"42".to_owned()) + ); } #[test] diff --git a/toolchains/dotnet/src/eval_cache.rs b/toolchains/dotnet/src/eval_cache.rs index 350639ad..eb433da2 100644 --- a/toolchains/dotnet/src/eval_cache.rs +++ b/toolchains/dotnet/src/eval_cache.rs @@ -89,8 +89,11 @@ fn push_framed(buffer: &mut String, file: &VirtualPath) { } /// Digest of everything that can change a project's evaluated package set: -/// its project files, plus every config file from the project directory up to -/// the workspace root. +/// its project files, every config file from the project directory up to the +/// workspace root, plus the configured `msbuildProperties` — a conditional +/// `PackageReference` gated on such a property evaluates differently when the +/// setting changes, so the properties must invalidate the cache like any file +/// edit would. /// /// Two things are deliberately *not* captured. Custom ``s outside the /// `Directory.Build.*` conventions — the same caveat that already applies to @@ -100,7 +103,11 @@ fn push_framed(buffer: &mut String, file: &VirtualPath) { /// before the cache *read* in `hash_task_contents`, and any asymmetry between /// the read and write keys turns the cache into a permanent miss — which is the /// per-project-evaluation cost this cache exists to avoid. -fn eval_cache_digest(project_root: &VirtualPath, workspace_root: &VirtualPath) -> String { +fn eval_cache_digest( + project_root: &VirtualPath, + workspace_root: &VirtualPath, + msbuild_properties: &BTreeMap, +) -> String { let mut buffer = String::new(); for file in find_project_files(project_root) { @@ -113,6 +120,16 @@ fn eval_cache_digest(project_root: &VirtualPath, workspace_root: &VirtualPath) - } } + // Framed like a file, under a name no real file can have (path separator). + for (name, value) in msbuild_properties { + buffer.push_str("msbuild-property/"); + buffer.push_str(name); + buffer.push(':'); + buffer.push_str(&value.len().to_string()); + buffer.push(':'); + buffer.push_str(value); + } + content_digest(&buffer) } @@ -125,12 +142,13 @@ pub fn write_eval_cache( workspace_root: &VirtualPath, project_id: &str, project_root: &VirtualPath, + msbuild_properties: &BTreeMap, packages: BTreeMap, ) { let file = eval_cache_file(workspace_root, project_id); let entry = EvalCacheEntry { - digest: eval_cache_digest(project_root, workspace_root), + digest: eval_cache_digest(project_root, workspace_root, msbuild_properties), packages, }; @@ -151,6 +169,7 @@ pub fn read_eval_cache( workspace_root: &VirtualPath, project_id: &str, project_root: &VirtualPath, + msbuild_properties: &BTreeMap, ) -> Option> { let file = eval_cache_file(workspace_root, project_id); @@ -160,7 +179,8 @@ pub fn read_eval_cache( let entry: EvalCacheEntry = serde_json::from_str(&fs::read_file(&file).ok()?).ok()?; - (entry.digest == eval_cache_digest(project_root, workspace_root)).then_some(entry.packages) + (entry.digest == eval_cache_digest(project_root, workspace_root, msbuild_properties)) + .then_some(entry.packages) } #[cfg(test)] @@ -180,11 +200,12 @@ mod tests { let sandbox = create_empty_sandbox(); let root = VirtualPath::Real(sandbox.path().into()); + let no_properties = BTreeMap::new(); sandbox.create_file("Directory.Build.props", ""); sandbox.create_file("Directory.Packages.props", ""); - let before = eval_cache_digest(&root, &root); + let before = eval_cache_digest(&root, &root, &no_properties); // A routine CPM migration: move the declaration to the other file. sandbox.create_file("Directory.Build.props", ""); @@ -192,8 +213,36 @@ mod tests { assert_ne!( before, - eval_cache_digest(&root, &root), + eval_cache_digest(&root, &root, &no_properties), "moving a declaration between config files must change the digest" ); } + + #[test] + fn msbuild_properties_invalidate_the_digest() { + let sandbox = create_empty_sandbox(); + let root = VirtualPath::Real(sandbox.path().into()); + + sandbox.create_file("Directory.Build.props", ""); + + let without = eval_cache_digest(&root, &root, &BTreeMap::new()); + let with = eval_cache_digest( + &root, + &root, + &BTreeMap::from([("SkipApiClientGen".to_owned(), "true".to_owned())]), + ); + + // A conditional PackageReference gated on the property would evaluate + // differently, so a cached set from one configuration must never be + // served under the other. + assert_ne!(without, with); + + let changed_value = eval_cache_digest( + &root, + &root, + &BTreeMap::from([("SkipApiClientGen".to_owned(), "false".to_owned())]), + ); + + assert_ne!(with, changed_value); + } } diff --git a/toolchains/dotnet/src/msbuild.rs b/toolchains/dotnet/src/msbuild.rs index 36650447..eb76c5d6 100644 --- a/toolchains/dotnet/src/msbuild.rs +++ b/toolchains/dotnet/src/msbuild.rs @@ -135,6 +135,22 @@ pub struct EvalEnv { /// projects. Leaving it unset would inherit moon's own working directory, /// making evaluation depend on where the user happened to run moon from. pub cwd: Option, + + /// Extra global properties for the evaluation, from the toolchain's + /// `msbuildProperties` setting, passed as `-p:NAME=VALUE`. Command-line + /// global properties propagate through the batched traversal's `` + /// task into every child project, so batch and per-project evaluation see + /// identical values. + pub msbuild_properties: BTreeMap, +} + +/// Render `msbuildProperties` as MSBuild `-p:` arguments, in deterministic +/// (BTreeMap) order. +pub fn msbuild_property_args(properties: &BTreeMap) -> Vec { + properties + .iter() + .map(|(name, value)| format!("-p:{name}={value}")) + .collect() } /// Deepest directory that contains all of the given workspace-relative @@ -396,12 +412,17 @@ pub fn detect_failed_projects(output: &str, project_paths: &[String]) -> Vec moon_pdk_api::ExecCommandInput { + input + .args + .extend(msbuild_property_args(&env.msbuild_properties)); + if let Some(root) = &env.dotnet_root { input.env.insert("DOTNET_ROOT".into(), root.clone()); input diff --git a/toolchains/dotnet/src/project_graph.rs b/toolchains/dotnet/src/project_graph.rs index 1a73d1b6..1f5ccaa1 100644 --- a/toolchains/dotnet/src/project_graph.rs +++ b/toolchains/dotnet/src/project_graph.rs @@ -534,6 +534,7 @@ pub fn extend_project_graph( &input.context.workspace_root, id.as_str(), &project_root, + &ctx.config.msbuild_properties, result.packages, ); } diff --git a/toolchains/dotnet/src/tier2.rs b/toolchains/dotnet/src/tier2.rs index 595ed583..dfc50e07 100644 --- a/toolchains/dotnet/src/tier2.rs +++ b/toolchains/dotnet/src/tier2.rs @@ -152,8 +152,8 @@ pub fn parse_manifest( .unwrap_or_else(|| input.context.workspace_root.clone()); // `parse_manifest` carries no toolchain config, so an explicit - // `dotnetRoot` cannot be honored here; the env var and the guarded - // `~/.dotnet` fallback still apply. + // `dotnetRoot` cannot be honored here (nor `msbuildProperties`); the env + // var and the guarded `~/.dotnet` fallback still apply. let eval_env = build_eval_env( &DotnetToolchainConfig::default(), manifest_dir, @@ -289,12 +289,16 @@ pub fn hash_task_contents( // per project here (the batch already evaluated them all at once); // 3. evaluating this project alone. let cache_key = format!("eval-packages:{}", input.project.id); + let config = parse_toolchain_config::(input.toolchain_config)?; let packages: BTreeMap = if let Some(cached) = var::get::(&cache_key)? { serde_json::from_str(&cached)? - } else if let Some(cached) = - read_eval_cache(workspace_root, input.project.id.as_str(), &project_root) - { + } else if let Some(cached) = read_eval_cache( + workspace_root, + input.project.id.as_str(), + &project_root, + &config.msbuild_properties, + ) { var::set(&cache_key, serde_json::to_string(&cached)?)?; cached @@ -304,7 +308,6 @@ pub fn hash_task_contents( let env = get_host_environment()?; if command_exists(&env, "dotnet") { - let config = parse_toolchain_config::(input.toolchain_config)?; let eval_env = build_eval_env(&config, project_root.clone(), workspace_root)?; evaluated_all = true; @@ -349,6 +352,7 @@ pub fn hash_task_contents( workspace_root, input.project.id.as_str(), &project_root, + &config.msbuild_properties, packages.clone(), ); } diff --git a/toolchains/dotnet/src/tier2_env.rs b/toolchains/dotnet/src/tier2_env.rs index aa62eb21..a5e372a0 100644 --- a/toolchains/dotnet/src/tier2_env.rs +++ b/toolchains/dotnet/src/tier2_env.rs @@ -227,6 +227,7 @@ pub fn build_eval_env( dotnet_root, dotnet_exe, cwd: Some(cwd), + msbuild_properties: config.msbuild_properties.clone(), }) } diff --git a/toolchains/dotnet/tests/msbuild_test.rs b/toolchains/dotnet/tests/msbuild_test.rs index b5a417d3..bd2039cc 100644 --- a/toolchains/dotnet/tests/msbuild_test.rs +++ b/toolchains/dotnet/tests/msbuild_test.rs @@ -299,4 +299,24 @@ https://aka.ms/dotnet/sdk-not-found"; "/home/x/app.csproj" ); } + + #[test] + fn renders_msbuild_properties_as_p_args_in_deterministic_order() { + use std::collections::BTreeMap; + + assert!(msbuild_property_args(&BTreeMap::new()).is_empty()); + + let args = msbuild_property_args(&BTreeMap::from([ + ("SkipApiClientGen".to_owned(), "true".to_owned()), + ("Configuration".to_owned(), "Release".to_owned()), + ])); + + // BTreeMap iteration is sorted, so the rendered order is stable across + // runs — evaluation commands must not differ between otherwise + // identical invocations. + assert_eq!( + args, + vec!["-p:Configuration=Release", "-p:SkipApiClientGen=true"] + ); + } } diff --git a/toolchains/dotnet/tests/tier2_test.rs b/toolchains/dotnet/tests/tier2_test.rs index c8f68ae7..b74a6776 100644 --- a/toolchains/dotnet/tests/tier2_test.rs +++ b/toolchains/dotnet/tests/tier2_test.rs @@ -423,6 +423,42 @@ mod dotnet_toolchain_tier2 { assert_eq!(cond.dependencies[0].id, Id::raw("deep")); } + /// The counterpart to the test above: same fixture, same gated + /// reference, but evaluated under a `msbuildProperties` value that + /// makes the condition false. Asserts the setting actually reaches + /// MSBuild and changes the graph, rather than only that it renders + /// into `-p:` arguments. + #[tokio::test(flavor = "multi_thread")] + async fn msbuild_properties_are_applied_to_the_evaluation() { + let sandbox = create_moon_sandbox("matrix"); + let plugin = sandbox.create_toolchain("dotnet").await; + + let mut input = ExtendProjectGraphInput::default(); + input + .project_sources + .insert(Id::raw("deep"), "nested/deep".into()); + input.project_sources.insert(Id::raw("cond"), "cond".into()); + input.toolchain_config = json!({ + "inferDependencies": true, + // Cond.csproj declares 1. A + // command-line global property cannot be overridden by the + // project, so this wins and the gated reference drops out. + "msbuildProperties": { "EnableDeepRef": "0" }, + }); + + let output = plugin.extend_project_graph(input).await; + + let cond = &output.extended_projects[&Id::raw("cond")]; + assert!( + cond.dependencies.is_empty(), + "expected the gated reference to be excluded, got {:?}", + cond.dependencies + .iter() + .map(|dep| dep.id.as_str()) + .collect::>() + ); + } + #[tokio::test(flavor = "multi_thread")] async fn unsatisfiable_global_json_pin_fails_with_guidance() { let sandbox = create_moon_sandbox("projects"); @@ -1049,6 +1085,35 @@ mod dotnet_toolchain_tier2 { assert!(!packages.contains_key("NeverPkg")); } + /// `msbuildProperties` can change the evaluated *package* set, not just + /// the dependency graph — which is why the properties belong in the + /// eval-cache digest. This is that claim tested against a real + /// evaluation rather than only at the digest level. + #[tokio::test(flavor = "multi_thread")] + async fn msbuild_properties_change_the_evaluated_package_set() { + let sandbox = create_moon_sandbox("matrix"); + let plugin = sandbox.create_toolchain("dotnet").await; + + let output = plugin + .hash_task_contents(HashTaskContentsInput { + project: fragment("cond", "cond"), + toolchain_config: json!({ + "msbuildProperties": { "EnableDeepRef": "0" }, + }), + ..Default::default() + }) + .await; + + let packages = output.contents[0]["packages"].as_object().unwrap(); + + // ExtraPkg is gated on the same property as the ProjectReference. + assert!(!packages.contains_key("ExtraPkg")); + + // Unconditional packages are unaffected, so this is the condition + // being re-evaluated rather than the set collapsing. + assert_eq!(packages["RootPkg"].as_str().unwrap(), "1.0.0"); + } + #[tokio::test(flavor = "multi_thread")] async fn central_package_management_hashes_via_props() { let sandbox = create_moon_sandbox("cpm");