@@ -111,8 +111,24 @@ async function promptUser(question: string, options: string[]): Promise<string>
111111 } ) ;
112112}
113113
114+ // Helper function to prompt yes/no
115+ async function promptYesNo ( question : string ) : Promise < boolean > {
116+ const rl = readline . createInterface ( {
117+ input : process . stdin ,
118+ output : process . stdout ,
119+ } ) ;
120+
121+ return new Promise ( ( resolve ) => {
122+ rl . question ( question + ' ' , ( answer ) => {
123+ rl . close ( ) ;
124+ resolve ( answer . toLowerCase ( ) === 'y' || answer . toLowerCase ( ) === 'yes' ) ;
125+ } ) ;
126+ } ) ;
127+ }
128+
114129class RealmSyncer extends RealmSyncBase {
115130 hasError = false ;
131+ failedPulls : { relativePath : string ; error : string } [ ] = [ ] ;
116132
117133 constructor (
118134 private syncOptions : SyncCommandOptions ,
@@ -123,6 +139,11 @@ class RealmSyncer extends RealmSyncBase {
123139 super ( syncOptions , matrixUrl , username , password ) ;
124140 }
125141
142+ // Public method to delete a file from the remote server
143+ async deleteRemoteFile ( relativePath : string ) : Promise < void > {
144+ return this . deleteFile ( relativePath ) ;
145+ }
146+
126147 private getConflictStrategy ( ) : ConflictStrategy {
127148 if ( this . syncOptions . preferLocal ) return 'local' ;
128149 if ( this . syncOptions . preferRemote ) return 'remote' ;
@@ -265,6 +286,7 @@ class RealmSyncer extends RealmSyncBase {
265286 }
266287
267288 // Execute pulls
289+ const failedPulls : { relativePath : string ; error : string } [ ] = [ ] ;
268290 if ( pullActions . length > 0 ) {
269291 console . log ( `\nPulling ${ pullActions . length } files from remote...` ) ;
270292 for ( const action of pullActions ) {
@@ -278,11 +300,19 @@ class RealmSyncer extends RealmSyncBase {
278300 } ;
279301 } catch ( error ) {
280302 this . hasError = true ;
303+ const errorMsg = error instanceof Error ? error . message : String ( error ) ;
281304 console . error ( `Error pulling ${ action . relativePath } :` , error ) ;
305+ // Track 500 errors for potential cleanup
306+ if ( errorMsg . includes ( '500' ) || errorMsg . includes ( 'Internal Server Error' ) ) {
307+ failedPulls . push ( { relativePath : action . relativePath , error : errorMsg } ) ;
308+ }
282309 }
283310 }
284311 }
285312
313+ // Store failed pulls for post-sync cleanup prompt
314+ this . failedPulls = failedPulls ;
315+
286316 // Handle local deletions (files deleted on server) - always sync these
287317 // Create checkpoint BEFORE deleting so we can recover
288318 if ( deleteLocalActions . length > 0 ) {
@@ -604,14 +634,10 @@ export async function syncCommand(
604634 explicitUrl : string ,
605635 options : SyncCommandOptionsInput ,
606636) : Promise < void > {
607- const matrixUrl = process . env . MATRIX_URL ;
608- const matrixUsername = process . env . MATRIX_USERNAME ;
609- const matrixPassword = process . env . MATRIX_PASSWORD ;
610-
611- if ( ! matrixUrl || ! matrixUsername || ! matrixPassword ) {
612- console . error ( 'Missing Matrix credentials in environment variables' ) ;
613- process . exit ( 1 ) ;
614- }
637+ // Determine workspace URL for profile detection (use explicit URL or resolve later)
638+ const urlForProfile = explicitUrl || ( workspaceRef . startsWith ( 'http' ) ? workspaceRef : '' ) ;
639+ const { matrixUrl, username : matrixUsername , password : matrixPassword } =
640+ await validateMatrixEnvVars ( urlForProfile ) ;
615641
616642 let localDir : string ;
617643 let workspaceUrl : string ;
@@ -675,6 +701,31 @@ export async function syncCommand(
675701 await syncer . initialize ( ) ;
676702 await syncer . sync ( ) ;
677703
704+ // Handle failed pulls - offer to delete broken files from server
705+ if ( syncer . failedPulls . length > 0 ) {
706+ console . log ( `\n⚠️ ${ syncer . failedPulls . length } file(s) failed to download (server error):` ) ;
707+ for ( const failed of syncer . failedPulls ) {
708+ console . log ( ` - ${ failed . relativePath } ` ) ;
709+ }
710+
711+ const shouldDelete = await promptYesNo (
712+ '\nThese files may be broken on the server. Delete them from remote? [y/N]'
713+ ) ;
714+
715+ if ( shouldDelete ) {
716+ console . log ( '\nDeleting broken files from server...' ) ;
717+ for ( const failed of syncer . failedPulls ) {
718+ try {
719+ await syncer . deleteRemoteFile ( failed . relativePath ) ;
720+ console . log ( ` Deleted: ${ failed . relativePath } ` ) ;
721+ } catch ( error ) {
722+ console . error ( ` Failed to delete ${ failed . relativePath } :` , error ) ;
723+ }
724+ }
725+ console . log ( 'Cleanup completed.' ) ;
726+ }
727+ }
728+
678729 if ( syncer . hasError ) {
679730 console . log ( 'Sync completed with errors. View logs for details.' ) ;
680731 process . exit ( 2 ) ;
0 commit comments