@@ -616,6 +616,109 @@ fn write_config_file(config_path: &Path, content: &str) -> RailResult<()> {
616616 Ok ( ( ) )
617617}
618618
619+ /// Standalone init that doesn't require WorkspaceContext
620+ ///
621+ /// This is used when init is called on a directory that may not have
622+ /// a valid Cargo workspace yet (e.g., empty workspace or invalid state).
623+ pub fn run_init_standalone (
624+ workspace_root : & Path ,
625+ output_path : & str ,
626+ force : bool ,
627+ _non_interactive : bool ,
628+ dry_run : bool ,
629+ ) -> RailResult < ( ) > {
630+ let config_path = workspace_root. join ( output_path) ;
631+
632+ // 1. Check for existing config
633+ if let Some ( existing) = check_existing_config ( workspace_root) {
634+ if !force {
635+ return Err ( RailError :: with_help (
636+ format ! (
637+ "Configuration already exists at: {}\n Use --force to overwrite or --output to specify a different location" ,
638+ existing. display( )
639+ ) ,
640+ "Example: cargo rail init --force" ,
641+ ) ) ;
642+ }
643+ if !dry_run {
644+ println ! ( "⚠️ Overwriting existing config at: {}" , existing. display( ) ) ;
645+ }
646+ }
647+
648+ // 2. Detection phase
649+ println ! ( "🔍 Detecting workspace configuration...\n " ) ;
650+
651+ let toolchain_config = detect_toolchain_config ( workspace_root) ?;
652+ let policy_config = detect_policy_config ( workspace_root) ?;
653+
654+ // Display detected settings
655+ println ! ( " Toolchain: {} ({})" , toolchain_config. channel, toolchain_config. profile) ;
656+ if let Some ( ref resolver) = policy_config. resolver {
657+ println ! ( " Resolver: {}" , resolver) ;
658+ }
659+ if let Some ( ref edition) = policy_config. edition {
660+ println ! ( " Edition: {}" , edition) ;
661+ }
662+ if let Some ( ref msrv) = policy_config. msrv {
663+ println ! ( " MSRV: {}" , msrv) ;
664+ }
665+ println ! ( ) ;
666+
667+ // 3. Build config
668+ let config = RailConfig {
669+ workspace : WorkspaceConfig {
670+ root : PathBuf :: from ( "." ) ,
671+ } ,
672+ toolchain : toolchain_config,
673+ policy : policy_config,
674+ unify : UnifyConfig {
675+ use_all_features : true ,
676+ sync_on_unify : true ,
677+ validate_targets : vec ! [ ] ,
678+ max_parallel_jobs : 0 ,
679+ pin_transitives : false ,
680+ pin_hosts : vec ! [ ] ,
681+ } ,
682+ security : SecurityConfig {
683+ ssh_key_path : None ,
684+ signing_key_path : None ,
685+ require_signed_commits : false ,
686+ pr_branch_pattern : "rail/sync/{crate}/{timestamp}" . to_string ( ) ,
687+ protected_branches : vec ! [ "main" . to_string( ) , "master" . to_string( ) ] ,
688+ } ,
689+ splits : vec ! [ ] ,
690+ } ;
691+
692+ // 4. Serialize with rich comments
693+ let config_toml = serialize_config_with_comments ( & config) ?;
694+
695+ // 5. Output
696+ if dry_run {
697+ println ! ( "--- {} ---" , output_path) ;
698+ println ! ( "{}" , config_toml) ;
699+ println ! ( "\n ✅ Dry-run complete (no files written)" ) ;
700+ } else {
701+ // Create parent directory if needed
702+ if let Some ( parent) = config_path. parent ( ) {
703+ fs:: create_dir_all ( parent) . map_err ( |e| {
704+ RailError :: with_help (
705+ format ! ( "Failed to create directory {}: {}" , parent. display( ) , e) ,
706+ "Check file permissions" ,
707+ )
708+ } ) ?;
709+ }
710+
711+ write_config_file ( & config_path, & config_toml) ?;
712+ println ! ( "✅ Created {}" , config_path. display( ) ) ;
713+ println ! ( "\n Next steps:" ) ;
714+ println ! ( " 1. Review and customize {}" , output_path) ;
715+ println ! ( " 2. Run `cargo rail unify` to normalize dependencies" ) ;
716+ println ! ( " 3. Run `cargo rail test` for change-based testing" ) ;
717+ }
718+
719+ Ok ( ( ) )
720+ }
721+
619722#[ cfg( test) ]
620723mod tests {
621724 use super :: * ;
0 commit comments