From ffe8311251eca801b17e5ccf1db7f19f6f674e7a Mon Sep 17 00:00:00 2001 From: Brooks Townsend Date: Wed, 27 Aug 2025 12:33:25 -0400 Subject: [PATCH 1/2] chore: ignore worktrees Signed-off-by: Brooks Townsend --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index 85bb7f21..2d1e999d 100644 --- a/.gitignore +++ b/.gitignore @@ -11,6 +11,9 @@ tests/output # Ignore Rust target directory target/ +# Ignore worktrees +worktrees/ + # AI things .claude CLAUDE.md From 690b13f231d30ac25b256e575163ee64d782a2bd Mon Sep 17 00:00:00 2001 From: Brooks Townsend Date: Wed, 27 Aug 2025 16:59:29 -0400 Subject: [PATCH 2/2] feat: add OCI reference support to plugin test command MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This implementation follows the same pattern as the `wash inspect` command to support different input types for the `wash plugin test` command: - **File**: Direct .wasm file path (existing behavior) - **Directory**: Project directory with build capability (existing behavior) - **OCI Reference**: Remote container registry references (new feature) Changes made: - Updated TestCommand.plugin field from PathBuf to String to handle OCI refs - Added OCI pull functionality with caching support - Enhanced error handling for different input types - Updated integration tests to work with new String type - Added debug logging for better troubleshooting The implementation reuses existing OCI infrastructure from the inspect command and maintains backward compatibility for file and directory inputs. Resolves #14 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- crates/wash/src/cli/plugin.rs | 93 ++++++++++++++++++++++++++++------- tests/integration_plugins.rs | 6 +-- 2 files changed, 77 insertions(+), 22 deletions(-) diff --git a/crates/wash/src/cli/plugin.rs b/crates/wash/src/cli/plugin.rs index 0513f0c8..8531a5fd 100644 --- a/crates/wash/src/cli/plugin.rs +++ b/crates/wash/src/cli/plugin.rs @@ -1,5 +1,5 @@ use std::collections::HashMap; -use std::{path::PathBuf, sync::Arc}; +use std::{path::Path, sync::Arc}; use anyhow::Context; use anyhow::bail; @@ -20,6 +20,7 @@ use wasmtime_wasi_http::io::TokioIo; use crate::runtime::bindings::plugin::wasmcloud::wash::types::Metadata; use crate::{ cli::{CliCommand, CliContext, CommandOutput, OutputKind, component_build::build_component}, + oci::{OCI_CACHE_DIR, OciConfig, pull_component}, plugin::{ InstallPluginOptions, PluginComponent, install_plugin, list_plugins, uninstall_plugin, }, @@ -275,9 +276,9 @@ pub struct ListCommand { #[derive(Args, Debug, Clone)] pub struct TestCommand { - /// Path to the component or component project to test + /// Plugin reference, which can be a local file path, project directory, or remote OCI reference #[clap(name = "plugin")] - pub plugin: PathBuf, + pub plugin: String, /// The hook types to test #[clap(name = "type", long = "hook", conflicts_with = "arg")] pub hooks: Vec, @@ -408,22 +409,76 @@ impl TestCommand { /// Handle the plugin test command #[instrument(level = "debug", skip_all, name = "plugin_test")] pub async fn handle(&self, ctx: &CliContext) -> anyhow::Result { - let wasm = if self.plugin.is_dir() { - let config = ctx - .ensure_config(Some(self.plugin.as_path())) - .await - .context("failed to load config")?; - let built_path = build_component(&self.plugin, ctx, &config) - .await - .context("Failed to build component from directory")?; - tokio::fs::read(&built_path.artifact_path) - .await - .context("Failed to read built component file")? + let path = Path::new(&self.plugin); + + let wasm = if path.exists() { + if path.is_file() { + // Direct file path - load it + debug!(plugin_ref = ?self.plugin, "loading plugin from file"); + tokio::fs::read(&self.plugin) + .await + .context("Failed to read component file")? + } else if path.is_dir() { + // Directory - check if it's a project and build it + debug!( + plugin_ref = ?self.plugin, + "directory detected, checking if it's a project" + ); + + // Check for project files + let project_files = [ + "Cargo.toml", + "go.mod", + "package.json", + "wasmcloud.toml", + ".wash/config.json", + ]; + let is_project = project_files.iter().any(|file| path.join(file).exists()); + + if is_project { + debug!( + plugin_ref = ?self.plugin, + "project directory detected, building component" + ); + + let config = ctx + .ensure_config(Some(path)) + .await + .context("Failed to load project configuration")?; + + let built_path = build_component(path, ctx, &config) + .await + .context("Failed to build component from project directory")?; + + debug!(artifact_path = ?built_path.artifact_path, "Component built successfully"); + + tokio::fs::read(&built_path.artifact_path) + .await + .context("Failed to read built component file")? + } else { + return Err(anyhow::anyhow!( + "Directory '{}' does not appear to be a project (no Cargo.toml, go.mod, package.json, wasmcloud.toml, or .wash/config.json found)", + self.plugin + )); + } + } else { + return Err(anyhow::anyhow!( + "Path '{}' exists but is neither a file nor directory", + self.plugin + )); + } } else { - tokio::fs::read(&self.plugin) - .await - .context("Failed to read component file")? - // TODO(#14): support OCI references too + debug!( + plugin_ref = ?self.plugin, + "Path does not exist locally, attempting to pull from remote" + ); + // Pull component from remote + pull_component( + &self.plugin, + OciConfig::new_with_cache(ctx.cache_dir().join(OCI_CACHE_DIR)), + ) + .await + .context("Failed to pull plugin from remote")? }; let mut output = String::new(); @@ -484,7 +539,7 @@ impl TestCommand { Ok(CommandOutput::ok( output, Some(json!({ - "plugin_path": self.plugin.to_string_lossy(), + "plugin_path": self.plugin, "args": self.args, "hooks": self.hooks, "metadata": component.metadata, diff --git a/tests/integration_plugins.rs b/tests/integration_plugins.rs index eb0576e1..7ad5fd4e 100644 --- a/tests/integration_plugins.rs +++ b/tests/integration_plugins.rs @@ -88,7 +88,7 @@ async fn test_plugin_test_inspect_comprehensive() -> Result<()> { }; let test_cmd_with_command = TestCommand { - plugin: inspect_plugin_path.clone(), + plugin: inspect_plugin_path.to_string_lossy().to_string(), args: vec![component_arg.clone()], hooks: vec![], }; @@ -118,7 +118,7 @@ async fn test_plugin_test_inspect_comprehensive() -> Result<()> { // Test 3: Plugin test with --hook afterdev eprintln!("🔍 Test 3: Plugin test with AfterDev hook"); let test_cmd_with_hook = TestCommand { - plugin: inspect_plugin_path.clone(), + plugin: inspect_plugin_path.to_string_lossy().to_string(), args: vec![], hooks: vec![HookType::AfterDev], }; @@ -138,7 +138,7 @@ async fn test_plugin_test_inspect_comprehensive() -> Result<()> { // Test 4: Plugin test with both command and AfterDev hook eprintln!("🔍 Test 4: Plugin test with both component inspection and AfterDev hook"); let test_cmd_with_both = TestCommand { - plugin: inspect_plugin_path.clone(), + plugin: inspect_plugin_path.to_string_lossy().to_string(), args: vec![component_arg.clone()], hooks: vec![HookType::AfterDev], };