|
1 | 1 | use std::fs; |
| 2 | +use std::io::{self, Write}; |
2 | 3 | use std::path::{Path, PathBuf}; |
3 | 4 | use std::process::ExitCode; |
4 | 5 |
|
@@ -39,19 +40,22 @@ const PACKAGE_TARGETS: &[PackageTarget] = &[ |
39 | 40 | ]; |
40 | 41 |
|
41 | 42 | pub fn execute(args: &AssembleNpmArgs) -> ExitCode { |
| 43 | + let mut stderr = io::stderr().lock(); |
| 44 | + execute_with_stderr(args, &mut stderr) |
| 45 | +} |
| 46 | + |
| 47 | +fn execute_with_stderr(args: &AssembleNpmArgs, stderr: &mut impl Write) -> ExitCode { |
42 | 48 | match assemble_packages(args) { |
43 | 49 | Ok(copied) => { |
44 | | - // Use stderr: this binary's primary mode is the MCP stdio server, |
45 | | - // and stdout is reserved for JSON-RPC framing. Routing all |
46 | | - // assemble-npm chatter to stderr keeps stdout protocol-safe even |
47 | | - // if the subcommand is ever invoked from a wrapped context. |
| 50 | + // Fixes #225: stdout is reserved for MCP JSON-RPC framing, so even |
| 51 | + // package-hydration status output must stay off stdout. |
48 | 52 | for path in copied { |
49 | | - eprintln!("Hydrated {}", path.display()); |
| 53 | + let _ = writeln!(stderr, "Hydrated {}", path.display()); |
50 | 54 | } |
51 | 55 | ExitCode::SUCCESS |
52 | 56 | } |
53 | 57 | Err(error) => { |
54 | | - eprintln!("Error: {error}"); |
| 58 | + let _ = writeln!(stderr, "Error: {error}"); |
55 | 59 | ExitCode::FAILURE |
56 | 60 | } |
57 | 61 | } |
@@ -187,3 +191,26 @@ fn set_executable_permissions(path: &Path) -> Result<(), String> { |
187 | 191 | fn set_executable_permissions(_path: &Path) -> Result<(), String> { |
188 | 192 | Ok(()) |
189 | 193 | } |
| 194 | + |
| 195 | +#[cfg(test)] |
| 196 | +mod tests { |
| 197 | + use super::*; |
| 198 | + |
| 199 | + #[test] |
| 200 | + fn assemble_npm_reports_errors_to_stderr_only() { |
| 201 | + let mut stderr = Vec::new(); |
| 202 | + let args = AssembleNpmArgs { |
| 203 | + artifacts_dir: None, |
| 204 | + profile: "missing-profile-for-issue-225".to_string(), |
| 205 | + }; |
| 206 | + |
| 207 | + let exit = execute_with_stderr(&args, &mut stderr); |
| 208 | + |
| 209 | + assert_eq!(exit, ExitCode::FAILURE); |
| 210 | + let stderr = String::from_utf8(stderr).expect("stderr output should be UTF-8"); |
| 211 | + assert!( |
| 212 | + stderr.contains("Error: Missing local host binary"), |
| 213 | + "expected assemble-npm errors to be written to stderr, got: {stderr}" |
| 214 | + ); |
| 215 | + } |
| 216 | +} |
0 commit comments