@@ -30,6 +30,10 @@ pub struct BuildOptions {
3030 /// typecheck, it is skipped and a `TypecheckTimeout` error is recorded.
3131 /// `None` means no timeout (the default).
3232 pub module_timeout : Option < std:: time:: Duration > ,
33+
34+ /// Output directory for generated JavaScript files.
35+ /// `None` means skip codegen. `Some(path)` writes JS to `path/<Module.Name>/index.js`.
36+ pub output_dir : Option < PathBuf > ,
3337}
3438
3539// ===== Public types =====
@@ -89,7 +93,7 @@ fn extract_foreign_import_names(module: &Module) -> Vec<String> {
8993// ===== Public API =====
9094
9195/// Build all PureScript modules matching the given glob patterns.
92- pub fn build ( globs : & [ & str ] ) -> BuildResult {
96+ pub fn build ( globs : & [ & str ] , output_dir : Option < PathBuf > ) -> BuildResult {
9397 let build_start = Instant :: now ( ) ;
9498 let mut build_errors = Vec :: new ( ) ;
9599
@@ -158,7 +162,12 @@ pub fn build(globs: &[&str]) -> BuildResult {
158162 . map ( |( k, v) | ( k. as_str ( ) , v. as_str ( ) ) )
159163 . collect ( ) ;
160164
161- let mut result = build_from_sources_with_js ( & source_refs, & Some ( js_refs) , None ) . 0 ;
165+ let options = BuildOptions {
166+ output_dir,
167+ ..Default :: default ( )
168+ } ;
169+ let mut result =
170+ build_from_sources_with_options ( & source_refs, & Some ( js_refs) , None , & options) . 0 ;
162171 // Prepend file-level errors before source-level errors
163172 build_errors. append ( & mut result. build_errors ) ;
164173 result. build_errors = build_errors;
@@ -658,6 +667,94 @@ pub fn build_from_sources_with_options(
658667 ) ;
659668 } // end if js_sources.is_some()
660669
670+ // Phase 6: Code generation (only when output_dir is specified)
671+ if let Some ( ref output_dir) = options. output_dir {
672+ log:: debug!( "Phase 6: JavaScript code generation to {}" , output_dir. display( ) ) ;
673+ let phase_start = Instant :: now ( ) ;
674+ let mut codegen_count = 0 ;
675+
676+ // Build a set of module names that typechecked successfully (zero errors)
677+ let ok_modules: HashSet < String > = module_results
678+ . iter ( )
679+ . filter ( |m| m. type_errors . is_empty ( ) )
680+ . map ( |m| m. module_name . clone ( ) )
681+ . collect ( ) ;
682+
683+ for pm in & parsed {
684+ if !ok_modules. contains ( & pm. module_name ) {
685+ log:: debug!( " skipping {} (has type errors)" , pm. module_name) ;
686+ continue ;
687+ }
688+
689+ // Look up this module's exports from the registry
690+ let module_exports = match registry. lookup ( & pm. module_parts ) {
691+ Some ( exports) => exports,
692+ None => {
693+ log:: debug!( " skipping {} (no exports in registry)" , pm. module_name) ;
694+ continue ;
695+ }
696+ } ;
697+
698+ let has_ffi = pm. js_source . is_some ( ) ;
699+
700+ log:: debug!( " generating JS for {}" , pm. module_name) ;
701+ let js_module = crate :: codegen:: js:: module_to_js (
702+ & pm. module ,
703+ & pm. module_name ,
704+ & pm. module_parts ,
705+ module_exports,
706+ & registry,
707+ has_ffi,
708+ ) ;
709+
710+ let js_text = crate :: codegen:: printer:: print_module ( & js_module) ;
711+
712+ // Write output/<Module.Name>/index.js
713+ let module_dir = output_dir. join ( & pm. module_name ) ;
714+ if let Err ( e) = std:: fs:: create_dir_all ( & module_dir) {
715+ log:: debug!( " failed to create dir {}: {}" , module_dir. display( ) , e) ;
716+ build_errors. push ( BuildError :: FileReadError {
717+ path : module_dir. clone ( ) ,
718+ error : format ! ( "Failed to create output directory: {e}" ) ,
719+ } ) ;
720+ continue ;
721+ }
722+
723+ let index_path = module_dir. join ( "index.js" ) ;
724+ if let Err ( e) = std:: fs:: write ( & index_path, & js_text) {
725+ log:: debug!( " failed to write {}: {}" , index_path. display( ) , e) ;
726+ build_errors. push ( BuildError :: FileReadError {
727+ path : index_path,
728+ error : format ! ( "Failed to write JS output: {e}" ) ,
729+ } ) ;
730+ continue ;
731+ }
732+ log:: debug!( " wrote {} ({} bytes)" , index_path. display( ) , js_text. len( ) ) ;
733+
734+ // Copy FFI companion file
735+ if let Some ( ref js_src) = pm. js_source {
736+ let foreign_path = module_dir. join ( "foreign.js" ) ;
737+ if let Err ( e) = std:: fs:: write ( & foreign_path, js_src) {
738+ log:: debug!( " failed to write {}: {}" , foreign_path. display( ) , e) ;
739+ build_errors. push ( BuildError :: FileReadError {
740+ path : foreign_path,
741+ error : format ! ( "Failed to write foreign JS: {e}" ) ,
742+ } ) ;
743+ continue ;
744+ }
745+ log:: debug!( " copied foreign.js for {}" , pm. module_name) ;
746+ }
747+
748+ codegen_count += 1 ;
749+ }
750+
751+ log:: debug!(
752+ "Phase 6 complete: generated JS for {} modules in {:.2?}" ,
753+ codegen_count,
754+ phase_start. elapsed( )
755+ ) ;
756+ }
757+
661758 log:: debug!(
662759 "Build pipeline finished in {:.2?} ({} modules, {} errors)" ,
663760 pipeline_start. elapsed( ) ,
0 commit comments