11use anyhow:: { Context , Result } ;
22use clap:: { Args , ValueEnum } ;
3-
3+ use inquire :: Confirm ;
44use log:: { debug, info, warn} ;
55use pcb_kicad:: { KiCadCliBuilder , PythonScriptBuilder } ;
66use pcb_ui:: { Colorize , Spinner , Style , StyledText } ;
@@ -13,7 +13,7 @@ use pcb_zen_core::{EvalOutput, WithDiagnostics};
1313use crate :: workspace:: { gather_workspace_info, WorkspaceInfo } ;
1414
1515use std:: fs;
16- use std:: io:: Write ;
16+ use std:: io:: { IsTerminal , Write } ;
1717
1818use chrono:: Utc ;
1919use std:: path:: { Path , PathBuf } ;
@@ -136,6 +136,10 @@ pub struct ReleaseArgs {
136136 /// Exclude specific manufacturing artifacts from the release (can be specified multiple times)
137137 #[ arg( long, value_enum) ]
138138 pub exclude : Vec < ArtifactType > ,
139+
140+ /// Skip confirmation prompt when warnings are present during validation
141+ #[ arg( long) ]
142+ pub yes : bool ,
139143}
140144
141145/// All information gathered during the release preparation phase
@@ -160,6 +164,8 @@ pub struct ReleaseInfo {
160164 pub output_dir : PathBuf ,
161165 /// Name of the output .zip file
162166 pub output_name : String ,
167+ /// Skip confirmation prompt for warnings
168+ pub yes : bool ,
163169}
164170
165171type TaskFn = fn ( & ReleaseInfo ) -> Result < ( ) > ;
@@ -205,43 +211,55 @@ fn get_manufacturing_tasks(excluded: &[ArtifactType]) -> Vec<(&'static str, Task
205211fn execute_tasks ( info : & ReleaseInfo , tasks : & [ ( & str , TaskFn ) ] ) -> Result < ( ) > {
206212 for ( name, task) in tasks {
207213 let spinner = Spinner :: builder ( * name) . start ( ) ;
208- let res = task ( info) ;
209-
210214 spinner. finish ( ) ;
211- match res {
212- Ok ( ( ) ) => eprintln ! ( "{} {name}" , "✓" . green( ) ) ,
213- Err ( e) => {
214- eprintln ! ( "{} {name} failed" , "✗" . red( ) ) ;
215- return Err ( e. context ( format ! ( "{name} failed" ) ) ) ;
216- }
217- }
215+
216+ task ( info) ?;
217+ eprintln ! ( "{} {name}" , "✓" . green( ) ) ;
218218 }
219219 Ok ( ( ) )
220220}
221221
222222pub fn execute ( args : ReleaseArgs ) -> Result < ( ) > {
223- // Gather all release information based on whether board or file was provided
224223 let release_info = {
225224 let info_spinner = Spinner :: builder ( "Gathering release information" ) . start ( ) ;
226- let info = if let Some ( board_name) = & args. board {
227- gather_release_info (
228- board_name. clone ( ) ,
229- args. path . clone ( ) ,
230- args. source_only ,
231- args. output_dir . clone ( ) ,
232- args. output_name . clone ( ) ,
233- ) ?
225+
226+ // Gather workspace info and evaluate the zen file
227+ let ( workspace, board_name) = if let Some ( board_name) = & args. board {
228+ let start_path = args. path . as_deref ( ) . unwrap_or ( "." ) ;
229+ let workspace_info =
230+ get_workspace_info ( & DefaultFileProvider :: new ( ) , Path :: new ( start_path) ) ?;
231+ let board_info = workspace_info. find_board_by_name ( board_name) ?;
232+ let zen_path = board_info. absolute_zen_path ( & workspace_info. root ) ;
233+ let workspace = gather_workspace_info ( zen_path, true ) ?;
234+ ( workspace, board_name. clone ( ) )
234235 } else if let Some ( zen_file) = & args. file {
235- gather_release_info_from_file (
236- zen_file. clone ( ) ,
237- args. source_only ,
238- args. output_dir . clone ( ) ,
239- args. output_name . clone ( ) ,
240- ) ?
236+ let workspace = gather_workspace_info ( zen_file. clone ( ) , true ) ?;
237+ let board_name = workspace. board_display_name ( ) ;
238+ ( workspace, board_name)
241239 } else {
242240 unreachable ! ( "Either board or file must be provided due to clap validation" )
243241 } ;
242+
244243 info_spinner. finish ( ) ;
244+
245+ // Render diagnostics and fail early if there are errors
246+ let diagnostics = & workspace. eval_result . diagnostics ;
247+ if diagnostics. has_errors ( ) || workspace. eval_result . output . is_none ( ) {
248+ let mut diagnostics = workspace. eval_result . diagnostics . clone ( ) ;
249+ let passes = crate :: build:: create_diagnostics_passes ( & [ ] ) ;
250+ diagnostics. apply_passes ( & passes) ;
251+ anyhow:: bail!( "Evaluation failed" ) ;
252+ }
253+
254+ let info = build_release_info (
255+ workspace,
256+ board_name,
257+ args. source_only ,
258+ args. output_dir . clone ( ) ,
259+ args. output_name . clone ( ) ,
260+ args. yes ,
261+ ) ?;
262+
245263 eprintln ! ( "{} Release information gathered" , "✓" . green( ) ) ;
246264 info
247265 } ;
@@ -279,39 +297,14 @@ pub fn execute(args: ReleaseArgs) -> Result<()> {
279297 Ok ( ( ) )
280298}
281299
282- /// Gather all information needed for the release using board name discovery
283- fn gather_release_info (
284- board_name : String ,
285- path : Option < String > ,
286- source_only : bool ,
287- output_dir : Option < PathBuf > ,
288- output_name : Option < String > ,
289- ) -> Result < ReleaseInfo > {
290- debug ! ( "Starting release information gathering" ) ;
291-
292- // Get workspace info using discovery
293- let start_path = path. as_deref ( ) . unwrap_or ( "." ) ;
294- let workspace_info = get_workspace_info ( & DefaultFileProvider :: new ( ) , Path :: new ( start_path) ) ?;
295-
296- // Find the zen file for the given board
297- let board_info = workspace_info. find_board_by_name ( & board_name) ?;
298-
299- let zen_path = board_info. absolute_zen_path ( & workspace_info. root ) ;
300-
301- // Use common workspace info gathering with the found zen file
302- let workspace = gather_workspace_info ( zen_path, true ) ?;
303-
304- // Use shared helper to build release info
305- build_release_info ( workspace, board_name, source_only, output_dir, output_name)
306- }
307-
308300/// Build ReleaseInfo from workspace info and other parameters
309301fn build_release_info (
310302 workspace : WorkspaceInfo ,
311303 board_name : String ,
312304 source_only : bool ,
313305 output_dir : Option < PathBuf > ,
314306 output_name : Option < String > ,
307+ yes : bool ,
315308) -> Result < ReleaseInfo > {
316309 // Get version and git hash from git
317310 let ( version, git_hash) = git_version_and_hash ( & workspace. config . root , & board_name) ?;
@@ -373,30 +366,10 @@ fn build_release_info(
373366 kind,
374367 output_dir,
375368 output_name,
369+ yes,
376370 } )
377371}
378372
379- /// Gather all information needed for the release using direct zen file path
380- fn gather_release_info_from_file (
381- zen_file_path : PathBuf ,
382- source_only : bool ,
383- output_dir : Option < PathBuf > ,
384- output_name : Option < String > ,
385- ) -> Result < ReleaseInfo > {
386- debug ! (
387- "Starting release information gathering from file: {}" ,
388- zen_file_path. display( )
389- ) ;
390-
391- // Use common workspace info gathering with the zen file path
392- let workspace = gather_workspace_info ( zen_file_path, true ) ?;
393-
394- // Get board name from workspace or fallback to zen file stem
395- let board_name = workspace. board_display_name ( ) ;
396-
397- build_release_info ( workspace, board_name, source_only, output_dir, output_name)
398- }
399-
400373/// Display all the gathered release information
401374fn display_release_info ( info : & ReleaseInfo , format : ReleaseOutputFormat ) {
402375 let release_type = match info. kind {
@@ -599,7 +572,7 @@ pub fn extract_layout_path(zen_path: &Path, eval: &WithDiagnostics<EvalOutput>)
599572 let output = eval
600573 . output
601574 . as_ref ( )
602- . context ( "No output in evaluation result " ) ?;
575+ . context ( "Evaluation failed - see diagnostics above " ) ?;
603576 let properties = output. sch_module . properties ( ) ;
604577
605578 let layout_path_value = properties. get ( "layout_path" )
@@ -867,22 +840,44 @@ fn validate_build(info: &ReleaseInfo) -> Result<()> {
867840
868841 debug ! ( "Validating build of: {}" , staged_zen_path. display( ) ) ;
869842
870- // Use build function with strict settings - offline mode and deny warnings
843+ let file_name = staged_zen_path. file_name ( ) . unwrap ( ) . to_string_lossy ( ) ;
844+
845+ // Use build function with offline mode but allow warnings
871846 let mut has_errors = false ;
847+ let mut has_warnings = false ;
872848 let _schematic = crate :: build:: build (
873849 & staged_zen_path,
874850 true , // offline mode since all dependencies should be vendored
875- crate :: build:: create_diagnostics_passes ( & [ ] ) , // no suppression for release
876- true , // deny_warnings
851+ crate :: build:: create_diagnostics_passes ( & [ ] ) ,
852+ false , // don't deny warnings - we'll prompt user instead
877853 & mut has_errors,
854+ & mut has_warnings,
878855 ) ;
879856
880857 if has_errors {
881- anyhow:: bail!(
882- "Build validation failed for staged zen file: {}" ,
883- staged_zen_path. display( )
884- ) ;
858+ std:: process:: exit ( 1 ) ;
885859 }
860+
861+ // Handle warnings if present and --yes flag wasn't passed
862+ if has_warnings && !info. yes {
863+ // Non-interactive if stdin OR stdout is not a terminal
864+ if !std:: io:: stdin ( ) . is_terminal ( ) || !std:: io:: stdout ( ) . is_terminal ( ) {
865+ eprintln ! (
866+ "{} {}: Build failed" ,
867+ pcb_ui:: icons:: error( ) ,
868+ file_name. with_style( Style :: Red ) . bold( )
869+ ) ;
870+ std:: process:: exit ( 1 ) ;
871+ }
872+ let confirmed =
873+ Confirm :: new ( "Build completed with warnings. Do you want to proceed with the release?" )
874+ . with_default ( true )
875+ . prompt ( ) ?;
876+ if !confirmed {
877+ std:: process:: exit ( 1 ) ;
878+ }
879+ }
880+
886881 Ok ( ( ) )
887882}
888883
0 commit comments