diff --git a/CHANGELOG.md b/CHANGELOG.md index e2d6b373a..8ea62f6f1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ - Added a new setting to `.prototools`, `settings.builtin-plugins`, that can be used to disable all built-in plugins, or only allow a few select plugins. - Supports a boolean or list of plugin names. - All are enabled by default for backwards compatibility. +- Updated `github://` plugin locators to support monorepos. Append the project name (that tags are prefixed with) to the path: `github://moonrepo/tools/node_tool` - Merged `proto use` and `proto install` commands. If no arguments are provided to `proto install`, it will install all configured tools. ## 0.38.4 diff --git a/crates/core/tests/proto_config_test.rs b/crates/core/tests/proto_config_test.rs index ec143e11b..3c12ba852 100644 --- a/crates/core/tests/proto_config_test.rs +++ b/crates/core/tests/proto_config_test.rs @@ -155,9 +155,9 @@ bar = "https://moonrepo.dev/path/file.wasm" ( Id::raw("foo"), PluginLocator::GitHub(Box::new(GitHubLocator { - file_prefix: "foo_plugin".into(), repo_slug: "moonrepo/foo".into(), tag: None, + project_name: None })) ), ]) diff --git a/crates/warpgate-api/src/locator.rs b/crates/warpgate-api/src/locator.rs index 16eeb6eec..9252f6d5a 100644 --- a/crates/warpgate-api/src/locator.rs +++ b/crates/warpgate-api/src/locator.rs @@ -6,25 +6,14 @@ use std::str::FromStr; /// A GitHub release locator. #[derive(Clone, Debug, Default, Eq, PartialEq)] pub struct GitHubLocator { - /// Name of asset without extension. - /// Defaults to `_plugin`. - pub file_prefix: String, - /// Organization and repository slug: `owner/repo`. pub repo_slug: String, - /// Release tag to use. Defaults to `latest`. + /// Explicit release tag to use. Defaults to `latest`. pub tag: Option, -} - -impl GitHubLocator { - pub fn extract_prefix_from_slug(slug: &str) -> &str { - slug.split('/').next().expect("Expected an owner scope!") - } - pub fn extract_suffix_from_slug(slug: &str) -> &str { - slug.split('/').nth(1).expect("Expected a repository name!") - } + /// Project name to match tags against. Primarily used in monorepos. + pub project_name: Option, } /// Errors during plugin locator parsing. @@ -61,6 +50,7 @@ pub enum PluginLocator { /// github://owner/repo /// github://owner/repo@tag + /// github://owner/repo/project GitHub(Box), /// https://url/to/file.wasm @@ -70,18 +60,6 @@ pub enum PluginLocator { }, } -impl PluginLocator { - pub fn create_wasm_file_prefix(name: &str) -> String { - let mut name = name.to_lowercase().replace('-', "_"); - - if !name.ends_with("_plugin") { - name.push_str("_plugin"); - } - - name - } -} - #[cfg(feature = "schematic")] impl schematic::Schematic for PluginLocator { fn schema_name() -> Option { @@ -101,8 +79,13 @@ impl Display for PluginLocator { PluginLocator::Url { url } => write!(f, "{}", url), PluginLocator::GitHub(github) => write!( f, - "github://{}{}", + "github://{}{}{}", github.repo_slug, + github + .project_name + .as_deref() + .map(|n| format!("/{n}")) + .unwrap_or_default(), github .tag .as_deref() @@ -173,12 +156,11 @@ impl TryFrom for PluginLocator { } let mut parts = query.split('/'); - let org = parts.next().unwrap().to_owned(); - let repo = parts.next().unwrap().to_owned(); - let file = parts.next().map(|f| f.to_owned()); + let org = parts.next().unwrap_or_default().to_owned(); + let repo = parts.next().unwrap_or_default().to_owned(); + let prefix = parts.next().map(|f| f.to_owned()); - github.file_prefix = - file.unwrap_or_else(|| PluginLocator::create_wasm_file_prefix(&repo)); + github.project_name = prefix; github.repo_slug = format!("{org}/{repo}"); Ok(PluginLocator::GitHub(Box::new(github))) diff --git a/crates/warpgate-api/tests/locator_test.rs b/crates/warpgate-api/tests/locator_test.rs index 94413b495..0ddd4848e 100644 --- a/crates/warpgate-api/tests/locator_test.rs +++ b/crates/warpgate-api/tests/locator_test.rs @@ -25,9 +25,9 @@ mod locator { assert_eq!( PluginLocator::GitHub(Box::new(GitHubLocator { - file_prefix: "proto_plugin".into(), repo_slug: "moonrepo/proto".into(), tag: None, + project_name: None, })) .to_string(), "github://moonrepo/proto" @@ -35,13 +35,33 @@ mod locator { assert_eq!( PluginLocator::GitHub(Box::new(GitHubLocator { - file_prefix: "proto_plugin".into(), + repo_slug: "moonrepo/proto".into(), + tag: None, + project_name: Some("tool".into()), + })) + .to_string(), + "github://moonrepo/proto/tool" + ); + + assert_eq!( + PluginLocator::GitHub(Box::new(GitHubLocator { repo_slug: "moonrepo/proto".into(), tag: Some("latest".into()), + project_name: None, })) .to_string(), "github://moonrepo/proto@latest" ); + + assert_eq!( + PluginLocator::GitHub(Box::new(GitHubLocator { + repo_slug: "moonrepo/proto".into(), + tag: Some("latest".into()), + project_name: Some("tool".into()), + })) + .to_string(), + "github://moonrepo/proto/tool@latest" + ); } #[test] @@ -150,9 +170,9 @@ mod locator { assert_eq!( PluginLocator::try_from("github:moonrepo/bun".to_string()).unwrap(), PluginLocator::GitHub(Box::new(GitHubLocator { - file_prefix: "bun_plugin".into(), repo_slug: "moonrepo/bun".into(), tag: None, + project_name: None, })) ); } @@ -162,9 +182,9 @@ mod locator { assert_eq!( PluginLocator::try_from("github://moonrepo/bun".to_string()).unwrap(), PluginLocator::GitHub(Box::new(GitHubLocator { - file_prefix: "bun_plugin".into(), repo_slug: "moonrepo/bun".into(), tag: None, + project_name: None, })) ); } @@ -174,9 +194,9 @@ mod locator { assert_eq!( PluginLocator::try_from("github://moonrepo/tools/bun_tool".to_string()).unwrap(), PluginLocator::GitHub(Box::new(GitHubLocator { - file_prefix: "bun_tool".into(), repo_slug: "moonrepo/tools".into(), tag: None, + project_name: Some("bun_tool".into()), })) ); } @@ -186,9 +206,9 @@ mod locator { assert_eq!( PluginLocator::try_from("github://moonrepo/bun-plugin@latest".to_string()).unwrap(), PluginLocator::GitHub(Box::new(GitHubLocator { - file_prefix: "bun_plugin".into(), repo_slug: "moonrepo/bun-plugin".into(), tag: Some("latest".into()), + project_name: None, })) ); } @@ -198,9 +218,9 @@ mod locator { assert_eq!( PluginLocator::try_from("github://moonrepo/bun_plugin@v1.2.3".to_string()).unwrap(), PluginLocator::GitHub(Box::new(GitHubLocator { - file_prefix: "bun_plugin".into(), repo_slug: "moonrepo/bun_plugin".into(), tag: Some("v1.2.3".into()), + project_name: None, })) ); } @@ -211,9 +231,9 @@ mod locator { PluginLocator::try_from("github://moonrepo/tools/bun_tool@v1.2.3".to_string()) .unwrap(), PluginLocator::GitHub(Box::new(GitHubLocator { - file_prefix: "bun_tool".into(), repo_slug: "moonrepo/tools".into(), tag: Some("v1.2.3".into()), + project_name: Some("bun_tool".into()), })) ); } diff --git a/crates/warpgate/README.md b/crates/warpgate/README.md index 89b942d71..53f4075c9 100644 --- a/crates/warpgate/README.md +++ b/crates/warpgate/README.md @@ -67,16 +67,17 @@ Download an asset from a GitHub release. This approach communicates with the Git Defining a `GITHUB_TOKEN` environment variable is recommended to avoid rate limiting. ```rust -// github:org/repo -// github:org/repo@v1.2.3 +// github://org/repo +// github://org/repo@v1.2.3 +// github://org/repo/project PluginLocator::GitHub(GitHubLocator{ - file_prefix: "file_prefix".into(), repo_slug: "org/repo".into(), + project_name: None, tag: Some("v1.2.3".into()), // Latest if `None` }) ``` -> The `file_prefix` cannot be configured with the string format, and defaults to the repository name in snake_case, suffixed with `_plugin`. +> The `project_name` field exists to support monorepos. When defined, it will look for a tag/release that starts with the project name. For example, if the project name was `example_plugin`, it will match `example_plugin-v1.2.3` or `example_plugin@v1.2.3` tags. ## Extism plugin containers diff --git a/crates/warpgate/src/endpoints.rs b/crates/warpgate/src/endpoints.rs index d74b72604..73139bd10 100644 --- a/crates/warpgate/src/endpoints.rs +++ b/crates/warpgate/src/endpoints.rs @@ -12,6 +12,11 @@ pub struct GitHubApiAsset { pub name: String, } +#[derive(Debug, Deserialize)] +pub struct GitHubApiTag { + pub name: String, +} + #[derive(Default, Deserialize)] #[serde(default)] pub struct GitHubApiRelease { diff --git a/crates/warpgate/src/error.rs b/crates/warpgate/src/error.rs index 6d1fc9cd8..e019bb4d7 100644 --- a/crates/warpgate/src/error.rs +++ b/crates/warpgate/src/error.rs @@ -35,6 +35,14 @@ pub enum WarpgateError { )] SourceFileMissing { id: Id, path: PathBuf }, + #[diagnostic(code(plugin::github::asset_missing))] + #[error( + "Cannot download {} plugin from GitHub ({}), no tag found or provided.", + .id.style(Style::Id), + .repo_slug.style(Style::Id), + )] + GitHubTagMissing { id: Id, repo_slug: String }, + #[diagnostic(code(plugin::github::asset_missing))] #[error( "Cannot download {} plugin from GitHub ({}), no applicable asset found for release {}.", diff --git a/crates/warpgate/src/loader.rs b/crates/warpgate/src/loader.rs index 4dc29bf04..151f1f790 100644 --- a/crates/warpgate/src/loader.rs +++ b/crates/warpgate/src/loader.rs @@ -6,6 +6,7 @@ use crate::helpers::{ }; use crate::id::Id; use once_cell::sync::OnceCell; +use serde::de::DeserializeOwned; use sha2::{Digest, Sha256}; use starbase_archive::is_supported_archive_extension; use starbase_styles::color; @@ -252,65 +253,85 @@ impl PluginLoader { id: &Id, github: &GitHubLocator, ) -> miette::Result { - let (api_url, release_tag) = if let Some(tag) = &github.tag { - ( - format!( - "https://api.github.com/repos/{}/releases/tags/{tag}", - github.repo_slug, - ), - tag.to_owned(), - ) - } else { - ( - format!( - "https://api.github.com/repos/{}/releases/latest", - github.repo_slug, - ), - "latest".to_owned(), - ) - }; - - // Check the cache first using the API URL as the seed, + // Check the cache first using the repository slug as the seed, // so that we can avoid making unnecessary HTTP requests. - let plugin_path = self.create_cache_path(id, &api_url, release_tag == "latest"); + let plugin_path = self.create_cache_path( + id, + github.repo_slug.as_str(), + github.tag.is_none() && github.project_name.is_none(), + ); if self.is_cached(id, &plugin_path)? { return Ok(plugin_path); } - trace!( - id = id.as_str(), - api_url = &api_url, - release_tag = &release_tag, - "Attempting to download plugin from GitHub release", - ); - - let handle_error = |error: reqwest::Error| WarpgateError::Http { - error: Box::new(error), - url: api_url.clone(), - }; - if self.is_offline() { return Err(WarpgateError::InternetConnectionRequired { message: format!( "Unable to download plugin {} from GitHub.", PluginLocator::GitHub(Box::new(github.to_owned())) ), - url: api_url, + url: "https://api.github.com".into(), } .into()); } - // Otherwise make an HTTP request to the GitHub releases API, - // and loop through the assets to find a matching one. - let mut request = self.get_client()?.get(&api_url); + // Fetch all tags then find a matching tag + release + let tags_url = format!("https://api.github.com/repos/{}/tags", github.repo_slug); + let found_tag; - if let Ok(auth_token) = env::var("GITHUB_TOKEN") { - request = request.bearer_auth(auth_token); + trace!( + id = id.as_str(), + tag = github.tag.as_ref(), + tag_prefix = github.project_name.as_ref(), + tags_url = &tags_url, + "Attempting to find a matching tag", + ); + + if let Some(tag) = &github.tag { + found_tag = Some(tag.to_owned()) + } else if let Some(tag_prefix) = &github.project_name { + found_tag = self + .send_github_request::>(tags_url) + .await? + .into_iter() + .find(|row| { + row.name.starts_with(format!("{tag_prefix}@").as_str()) + || row.name.starts_with(format!("{tag_prefix}-").as_str()) + }) + .map(|row| row.name); + } else { + found_tag = Some("latest".into()); } - let response = request.send().await.map_err(handle_error)?; - let release: GitHubApiRelease = response.json().await.map_err(handle_error)?; + let Some(release_tag) = found_tag else { + return Err(WarpgateError::GitHubTagMissing { + id: id.to_owned(), + repo_slug: github.repo_slug.to_owned(), + } + .into()); + }; + + let release_url = if release_tag == "latest" { + format!( + "https://api.github.com/repos/{}/releases/latest", + github.repo_slug, + ) + } else { + format!( + "https://api.github.com/repos/{}/releases/tags/{release_tag}", + github.repo_slug, + ) + }; + + trace!( + id = id.as_str(), + release_url = &release_url, + release_tag = &release_tag, + "Attempting to download plugin from GitHub release", + ); + + let release: GitHubApiRelease = self.send_github_request(release_url).await?; // Find a direct WASM asset first for asset in &release.assets { @@ -328,20 +349,22 @@ impl PluginLoader { } // Otherwise an asset with a matching name and supported extension - for asset in release.assets { - if asset.name == github.file_prefix - || (asset.name.starts_with(&github.file_prefix) - && is_supported_archive_extension(&PathBuf::from(&asset.name))) - { - trace!( - id = id.as_str(), - asset = &asset.name, - "Found possible asset as an archive" - ); + if let Some(tag_prefix) = &github.project_name { + for asset in release.assets { + if &asset.name == tag_prefix + || (asset.name.starts_with(tag_prefix) + && is_supported_archive_extension(&PathBuf::from(&asset.name))) + { + trace!( + id = id.as_str(), + asset = &asset.name, + "Found possible asset as an archive" + ); - return self - .download_plugin(id, &asset.browser_download_url, plugin_path) - .await; + return self + .download_plugin(id, &asset.browser_download_url, plugin_path) + .await; + } } } @@ -352,4 +375,22 @@ impl PluginLoader { } .into()) } + + async fn send_github_request(&self, url: String) -> miette::Result { + let mut request = self.get_client()?.get(&url).query(&[("per_page", "100")]); + + if let Ok(auth_token) = env::var("GITHUB_TOKEN") { + request = request.bearer_auth(auth_token); + } + + let handle_error = |error: reqwest::Error| WarpgateError::Http { + error: Box::new(error), + url: url.clone(), + }; + + let response = request.send().await.map_err(handle_error)?; + let data: T = response.json().await.map_err(handle_error)?; + + Ok(data) + } } diff --git a/crates/warpgate/tests/loader_test.rs b/crates/warpgate/tests/loader_test.rs index b03488b60..c96a11796 100644 --- a/crates/warpgate/tests/loader_test.rs +++ b/crates/warpgate/tests/loader_test.rs @@ -119,9 +119,9 @@ mod loader { .load_plugin( Id::raw("test"), PluginLocator::GitHub(Box::new(GitHubLocator { - file_prefix: "bun_plugin.wasm".into(), repo_slug: "moonrepo/invalid-repo".into(), tag: None, + project_name: None, })), ) .await @@ -136,15 +136,15 @@ mod loader { .load_plugin( Id::raw("test"), PluginLocator::GitHub(Box::new(GitHubLocator { - file_prefix: "bun_plugin.wasm".into(), repo_slug: "moonrepo/bun-plugin".into(), tag: Some("v0.0.3".into()), + project_name: None, })), ) .await .unwrap(); - assert_eq!(path, sandbox.path().join("plugins/test-6858d7b8b0bcd96afd3da08c25cda7cfa2d25b8776fba1cbacea2391e81bdc1e.wasm")); + assert_eq!(path, sandbox.path().join("plugins/test-3659b10975b8c1f704254f47c17e93f76abf6878dfcab9f9b6346491cf5b5df1.wasm")); } #[tokio::test] @@ -155,15 +155,15 @@ mod loader { .load_plugin( Id::raw("test"), PluginLocator::GitHub(Box::new(GitHubLocator { - file_prefix: "bun_plugin.wasm".into(), repo_slug: "moonrepo/bun-plugin".into(), tag: None, + project_name: None, })), ) .await .unwrap(); - assert_eq!(path, sandbox.path().join("plugins/test-latest-fbd480065d33e0cb2cc9501b7f20fb7edd1a552f1c629dd8b35071f5bac4a0cb.wasm")); + assert_eq!(path, sandbox.path().join("plugins/test-latest-3659b10975b8c1f704254f47c17e93f76abf6878dfcab9f9b6346491cf5b5df1.wasm")); } } } diff --git a/registry/data/built-in-new.json b/registry/data/built-in-new.json deleted file mode 100644 index 83dfadad9..000000000 --- a/registry/data/built-in-new.json +++ /dev/null @@ -1,183 +0,0 @@ -{ - "$schema": "../schema.json", - "version": 1, - "plugins": [ - { - "id": "bun", - "locator": "github://moonrepo/tools/bun_tool", - "format": "wasm", - "name": "Bun", - "description": "Bun is an all-in-one runtime and toolset for JavaScript and TypeScript, powered by Zig and Webkit.", - "author": "moonrepo", - "homepageUrl": "https://bun.sh/", - "repositoryUrl": "https://github.com/moonrepo/tools", - "devicon": "bun", - "bins": ["bun", "bunx"], - "globalsDirs": ["~/.bun/bin"] - }, - { - "id": "deno", - "locator": "github://moonrepo/tools/deno_tool", - "format": "wasm", - "name": "Deno", - "description": "Deno is a secure runtime for JavaScript and TypeScript, powered by Rust and Chrome's V8 engine.", - "author": "moonrepo", - "homepageUrl": "https://deno.land/", - "repositoryUrl": "https://github.com/moonrepo/tools", - "devicon": "denojs", - "bins": ["deno"], - "detectionSources": [ - { - "file": ".dvmrc", - "url": "https://github.com/justjavac/dvm" - } - ], - "globalsDirs": ["$DENO_INSTALL_ROOT/bin", "$DENO_HOME/bin", "~/.deno/bin"] - }, - { - "id": "go", - "locator": "github://moonrepo/tools/go_tool", - "format": "wasm", - "name": "Go", - "description": "Go is a simple, secure, and fast systems language.", - "author": "moonrepo", - "homepageUrl": "https://go.dev/", - "repositoryUrl": "https://github.com/moonrepo/tools", - "devicon": "go", - "bins": ["go", "gofmt"], - "detectionSources": [ - { - "file": "go.work", - "url": "https://go.dev/doc/tutorial/workspaces" - }, - { - "file": "go.mod", - "url": "https://go.dev/doc/modules/gomod-ref" - } - ], - "globalsDirs": ["$GOBIN", "$GOROOT/bin", "$GOPATH/bin", "~/go/bin"] - }, - { - "id": "node", - "locator": "github://moonrepo/tools/node_tool", - "format": "wasm", - "name": "Node.js", - "description": "Node.js is a JavaScript runtime built on Chrome's V8 engine.", - "author": "moonrepo", - "homepageUrl": "https://nodejs.org/", - "repositoryUrl": "https://github.com/moonrepo/tools", - "bins": ["node"], - "detectionSources": [ - { - "file": ".nvmrc", - "url": "https://github.com/nvm-sh/nvm" - }, - { - "file": ".node-version", - "url": "https://github.com/nodenv/nodenv" - }, - { - "file": "package.json", - "label": "engines" - } - ], - "globalsDirs": ["~/.proto/tools/node/globals/bin"] - }, - { - "id": "npm", - "locator": "github://moonrepo/tools/node_depman_tool", - "format": "wasm", - "name": "npm", - "description": "A Node.js package manager.", - "author": "moonrepo", - "repositoryUrl": "https://github.com/moonrepo/tools", - "devicon": "npm", - "bins": ["npm", "npx", "node-gyp"], - "detectionSources": [ - { - "file": "package.json", - "label": "engines / package manager" - } - ], - "globalsDirs": ["~/.proto/tools/node/globals/bin"] - }, - { - "id": "pnpm", - "locator": "github://moonrepo/tools/node_depman_tool", - "format": "wasm", - "name": "pnpm", - "description": "A Node.js package manager.", - "author": "moonrepo", - "repositoryUrl": "https://github.com/moonrepo/tools", - "devicon": "pnpm", - "bins": ["pnpm", "pnpx"], - "detectionSources": [ - { - "file": "package.json", - "label": "engines / package manager" - } - ], - "globalsDirs": ["~/.proto/tools/node/globals/bin"] - }, - { - "id": "python", - "locator": "github://moonrepo/tools/python_tool", - "format": "wasm", - "name": "Python (experimental)", - "description": "Python is a high-level, general-purpose programming language.", - "author": "moonrepo", - "homepageUrl": "https://www.python.org/", - "repositoryUrl": "https://github.com/moonrepo/tools", - "bins": ["python", "pip"], - "detectionSources": [ - { - "file": ".python-version", - "url": "https://github.com/pyenv/pyenv" - } - ], - "globalsDirs": ["~/.proto/tools/python/x.x.x/install/bin"] - }, - { - "id": "rust", - "locator": "github://moonrepo/tools/rust_tool", - "format": "wasm", - "name": "Rust", - "description": "Rust is a blazingly fast and memory-efficient systems language.", - "author": "moonrepo", - "homepageUrl": "https://www.rust-lang.org/", - "repositoryUrl": "https://github.com/moonrepo/tools", - "bins": [], - "detectionSources": [ - { - "file": "rust-toolchain.toml" - }, - { - "file": "rust-toolchain" - } - ], - "globalsDirs": [ - "$CARGO_INSTALL_ROOT/bin", - "$CARGO_HOME/bin", - "~/.cargo/bin" - ] - }, - { - "id": "yarn", - "locator": "github://moonrepo/tools/node_depman_tool", - "format": "wasm", - "name": "Yarn", - "description": "A Node.js package manager.", - "author": "moonrepo", - "repositoryUrl": "https://github.com/moonrepo/tools", - "devicon": "yarn", - "bins": ["yarn", "yarnpkg"], - "detectionSources": [ - { - "file": "package.json", - "label": "engines / package manager" - } - ], - "globalsDirs": ["~/.proto/tools/node/globals/bin"] - } - ] -} diff --git a/registry/data/built-in.json b/registry/data/built-in.json index c45fc9e0c..9465902cf 100644 --- a/registry/data/built-in.json +++ b/registry/data/built-in.json @@ -4,13 +4,13 @@ "plugins": [ { "id": "bun", - "locator": "github://moonrepo/bun-plugin", + "locator": "github://moonrepo/tools/bun_tool", "format": "wasm", "name": "Bun", "description": "Bun is an all-in-one runtime and toolset for JavaScript and TypeScript, powered by Zig and Webkit.", "author": "moonrepo", "homepageUrl": "https://bun.sh/", - "repositoryUrl": "https://github.com/moonrepo/bun-plugin", + "repositoryUrl": "https://github.com/moonrepo/tools", "devicon": "bun", "bins": [ "bun", @@ -22,13 +22,13 @@ }, { "id": "deno", - "locator": "github://moonrepo/deno-plugin", + "locator": "github://moonrepo/tools/deno_tool", "format": "wasm", "name": "Deno", "description": "Deno is a secure runtime for JavaScript and TypeScript, powered by Rust and Chrome's V8 engine.", "author": "moonrepo", "homepageUrl": "https://deno.land/", - "repositoryUrl": "https://github.com/moonrepo/deno-plugin", + "repositoryUrl": "https://github.com/moonrepo/tools", "devicon": "denojs", "bins": [ "deno" @@ -47,13 +47,13 @@ }, { "id": "go", - "locator": "github://moonrepo/go-plugin", + "locator": "github://moonrepo/tools/go_tool", "format": "wasm", "name": "Go", "description": "Go is a simple, secure, and fast systems language.", "author": "moonrepo", "homepageUrl": "https://go.dev/", - "repositoryUrl": "https://github.com/moonrepo/go-plugin", + "repositoryUrl": "https://github.com/moonrepo/tools", "devicon": "go", "bins": [ "go", @@ -78,13 +78,13 @@ }, { "id": "node", - "locator": "github://moonrepo/node-plugin", + "locator": "github://moonrepo/tools/node_tool", "format": "wasm", "name": "Node.js", "description": "Node.js is a JavaScript runtime built on Chrome's V8 engine.", "author": "moonrepo", "homepageUrl": "https://nodejs.org/", - "repositoryUrl": "https://github.com/moonrepo/node-plugin", + "repositoryUrl": "https://github.com/moonrepo/tools", "bins": [ "node" ], @@ -108,12 +108,12 @@ }, { "id": "npm", - "locator": "github://moonrepo/node-plugin", + "locator": "github://moonrepo/tools/node_depman_tool", "format": "wasm", "name": "npm", "description": "A Node.js package manager.", "author": "moonrepo", - "repositoryUrl": "https://github.com/moonrepo/node-plugin", + "repositoryUrl": "https://github.com/moonrepo/tools", "devicon": "npm", "bins": [ "npm", @@ -132,12 +132,12 @@ }, { "id": "pnpm", - "locator": "github://moonrepo/node-plugin", + "locator": "github://moonrepo/tools/node_depman_tool", "format": "wasm", "name": "pnpm", "description": "A Node.js package manager.", "author": "moonrepo", - "repositoryUrl": "https://github.com/moonrepo/node-plugin", + "repositoryUrl": "https://github.com/moonrepo/tools", "devicon": "pnpm", "bins": [ "pnpm", @@ -155,13 +155,13 @@ }, { "id": "python", - "locator": "github://moonrepo/python-plugin", + "locator": "github://moonrepo/tools/python_tool", "format": "wasm", "name": "Python (experimental)", "description": "Python is a high-level, general-purpose programming language.", "author": "moonrepo", "homepageUrl": "https://www.python.org/", - "repositoryUrl": "https://github.com/moonrepo/python-plugin", + "repositoryUrl": "https://github.com/moonrepo/tools", "bins": [ "python", "pip" @@ -178,13 +178,13 @@ }, { "id": "rust", - "locator": "github://moonrepo/rust-plugin", + "locator": "github://moonrepo/tools/rust_tool", "format": "wasm", "name": "Rust", "description": "Rust is a blazingly fast and memory-efficient systems language.", "author": "moonrepo", "homepageUrl": "https://www.rust-lang.org/", - "repositoryUrl": "https://github.com/moonrepo/rust-plugin", + "repositoryUrl": "https://github.com/moonrepo/tools", "bins": [], "detectionSources": [ { @@ -202,12 +202,12 @@ }, { "id": "yarn", - "locator": "github://moonrepo/node-plugin", + "locator": "github://moonrepo/tools/node_depman_tool", "format": "wasm", "name": "Yarn", "description": "A Node.js package manager.", "author": "moonrepo", - "repositoryUrl": "https://github.com/moonrepo/node-plugin", + "repositoryUrl": "https://github.com/moonrepo/tools", "devicon": "yarn", "bins": [ "yarn",