@@ -1022,7 +1022,86 @@ fn deploy() -> Result<()> {
10221022 println ! ( " Deployment v{} created. Waiting for provisioning..." , version) ;
10231023 println ! ( ) ;
10241024
1025- // Stream deployment events via SSE
1025+ // Phase 1: Stream events until infra is ready (migrating state)
1026+ let phase1_status = stream_deployment_events ( & client, & pid) ?;
1027+
1028+ match phase1_status. as_str ( ) {
1029+ "migrating" => {
1030+ println ! ( ) ;
1031+ println ! ( " ▸ Infrastructure ready. Running migrations..." ) ;
1032+
1033+ // Get the SurrealDB URL from deployment status
1034+ if let Ok ( status_resp) = client. get ( & format ! ( "/v1/projects/{}/deployment" , pid) ) {
1035+ if let Ok ( status_data) = status_resp. into_json :: < serde_json:: Value > ( ) {
1036+ if let Some ( db_url) = status_data[ "urls" ] [ "surrealdb" ] . as_str ( ) {
1037+ // Run migrations against the cloud SurrealDB
1038+ let resolved = config. resolved_surrealdb ( ) ;
1039+ let surreal_client = crate :: surreal_client:: SurrealClient :: new (
1040+ & format ! ( "{}/sql" , db_url) ,
1041+ & resolved. namespace ,
1042+ & resolved. database ,
1043+ "root" ,
1044+ "root" ,
1045+ ) ;
1046+
1047+ let schema = config. resolved_schema ( ) ;
1048+ let config_dir = config_path. parent ( ) . unwrap_or ( std:: path:: Path :: new ( "." ) ) ;
1049+ let migrations_dir = config_dir. join ( & schema. migrations ) ;
1050+
1051+ if migrations_dir. exists ( ) {
1052+ match crate :: migrate:: apply ( & surreal_client, & migrations_dir) {
1053+ Ok ( ( ) ) => println ! ( " ▸ Migrations complete." ) ,
1054+ Err ( e) => println ! ( " ▸ Migration warning: {}" , e) ,
1055+ }
1056+ } else {
1057+ println ! ( " ▸ No migrations directory found, skipping." ) ;
1058+ }
1059+ }
1060+ }
1061+ }
1062+
1063+ // Phase 2: Finalize deployment (triggers app VM provisioning)
1064+ println ! ( " ▸ Deploying applications..." ) ;
1065+ client. post (
1066+ & format ! ( "/v1/projects/{}/deploy/finalize" , pid) ,
1067+ & serde_json:: json!( { } ) ,
1068+ ) ?;
1069+
1070+ // Stream events until fully running
1071+ let phase2_status = stream_deployment_events ( & client, & pid) ?;
1072+ match phase2_status. as_str ( ) {
1073+ "running" => {
1074+ println ! ( ) ;
1075+ println ! ( " Deployment is running!" ) ;
1076+ if let Ok ( status_resp) = client. get ( & format ! ( "/v1/projects/{}/deployment" , pid) ) {
1077+ if let Ok ( status) = status_resp. into_json :: < serde_json:: Value > ( ) {
1078+ print_deployment_details ( & status) ;
1079+ }
1080+ }
1081+ }
1082+ "failed" => bail ! ( "Deployment failed." ) ,
1083+ _ => bail ! ( "Deployment ended unexpectedly (status: {})" , phase2_status) ,
1084+ }
1085+ }
1086+ "running" => {
1087+ // No migration phase (legacy or no infra VMs)
1088+ println ! ( ) ;
1089+ println ! ( " Deployment is running!" ) ;
1090+ if let Ok ( status_resp) = client. get ( & format ! ( "/v1/projects/{}/deployment" , pid) ) {
1091+ if let Ok ( status) = status_resp. into_json :: < serde_json:: Value > ( ) {
1092+ print_deployment_details ( & status) ;
1093+ }
1094+ }
1095+ }
1096+ "failed" => bail ! ( "Deployment failed." ) ,
1097+ _ => bail ! ( "Deployment ended unexpectedly (status: {})" , phase1_status) ,
1098+ }
1099+
1100+ Ok ( ( ) )
1101+ }
1102+
1103+ /// Stream SSE deployment events and return the final status when the stream closes.
1104+ fn stream_deployment_events ( client : & CloudClient , pid : & str ) -> Result < String > {
10261105 let events_url = format ! (
10271106 "{}/v1/projects/{}/deployment/events" ,
10281107 client. base_url, pid
@@ -1038,7 +1117,6 @@ fn deploy() -> Result<()> {
10381117 use std:: io:: BufRead ;
10391118
10401119 let mut final_status = String :: new ( ) ;
1041- let mut final_error = String :: new ( ) ;
10421120
10431121 for line in reader. lines ( ) {
10441122 let line = match line {
@@ -1072,87 +1150,27 @@ fn deploy() -> Result<()> {
10721150 "deployment" => {
10731151 let status = event[ "status" ] . as_str ( ) . unwrap_or ( "unknown" ) ;
10741152 final_status = status. to_string ( ) ;
1075- if let Some ( err) = event[ "error" ] . as_str ( ) {
1076- if !err. is_empty ( ) {
1077- final_error = err. to_string ( ) ;
1078- }
1079- }
10801153 match status {
1081- "provisioning" => {
1082- println ! ( " ▸ Provisioning VMs..." ) ;
1083- }
1084- "running" | "failed" | "destroyed" => {
1085- // Stream will close, handled below
1086- }
1087- _ => {
1088- println ! ( " ▸ {}" , status) ;
1089- }
1154+ "provisioning" => println ! ( " ▸ Provisioning VMs..." ) ,
1155+ "deploying_apps" => println ! ( " ▸ Deploying applications..." ) ,
1156+ "migrating" | "running" | "failed" | "destroyed" => { }
1157+ _ => println ! ( " ▸ {}" , status) ,
10901158 }
10911159 }
10921160 _ => { }
10931161 }
10941162 }
10951163
1096- // Stream ended — check final state
1097- match final_status. as_str ( ) {
1098- "running" => {
1099- println ! ( ) ;
1100- println ! ( " Deployment is running!" ) ;
1101- // Fetch final details
1102- if let Ok ( status_resp) = client. get ( & format ! ( "/v1/projects/{}/deployment" , pid) ) {
1103- if let Ok ( status) = status_resp. into_json :: < serde_json:: Value > ( ) {
1104- print_deployment_details ( & status) ;
1105- }
1106- }
1107- }
1108- "failed" => {
1109- bail ! ( "Deployment failed: {}" , if final_error. is_empty( ) { "unknown error" } else { & final_error } ) ;
1110- }
1111- "destroyed" => {
1112- bail ! ( "Deployment was destroyed." ) ;
1113- }
1114- _ => {
1115- bail ! ( "Deployment stream ended unexpectedly (status: {})" , final_status) ;
1116- }
1117- }
1164+ Ok ( final_status)
11181165 }
1119- Err ( _) => {
1120- // Fallback: poll for status (server may not support SSE yet)
1121- loop {
1122- thread:: sleep ( Duration :: from_secs ( 2 ) ) ;
1123-
1124- let status_resp = client. get ( & format ! ( "/v1/projects/{}/deployment" , pid) ) ?;
1125- let status: serde_json:: Value =
1126- status_resp. into_json ( ) . context ( "Failed to parse status" ) ?;
1127-
1128- let dep_status = status[ "deployment" ] [ "status" ]
1129- . as_str ( )
1130- . unwrap_or ( "unknown" ) ;
1131-
1132- match dep_status {
1133- "running" => {
1134- println ! ( " Deployment is running!" ) ;
1135- print_deployment_details ( & status) ;
1136- return Ok ( ( ) ) ;
1137- }
1138- "failed" => {
1139- let error = status[ "deployment" ] [ "error" ]
1140- . as_str ( )
1141- . unwrap_or ( "unknown error" ) ;
1142- bail ! ( "Deployment failed: {}" , error) ;
1143- }
1144- "destroyed" => {
1145- bail ! ( "Deployment was destroyed." ) ;
1146- }
1147- _ => {
1148- print ! ( " Status: {}...\r " , dep_status) ;
1149- }
1150- }
1151- }
1166+ Err ( ureq:: Error :: Status ( code, resp) ) => {
1167+ let body = resp. into_string ( ) . unwrap_or_default ( ) ;
1168+ bail ! ( "Failed to stream events (HTTP {}): {}" , code, body) ;
1169+ }
1170+ Err ( ureq:: Error :: Transport ( t) ) => {
1171+ bail ! ( "Connection error: {}" , t) ;
11521172 }
11531173 }
1154-
1155- Ok ( ( ) )
11561174}
11571175
11581176fn status ( ) -> Result < ( ) > {
0 commit comments