@@ -23,6 +23,7 @@ const { VirtualProvider } = require('internal/vfs/provider');
2323const { VirtualFileHandle } = require ( 'internal/vfs/file_handle' ) ;
2424const {
2525 createEEXIST,
26+ createEINVAL,
2627 createEISDIR,
2728 createENOENT,
2829 createENOTDIR,
@@ -102,6 +103,28 @@ function methodOption(method) {
102103 return 'deflate' ;
103104}
104105
106+ function renameOptions ( entry ) {
107+ return {
108+ mode : entry . mode || undefined ,
109+ modified : entry . modified ,
110+ method : methodOption ( entry . method ) ,
111+ } ;
112+ }
113+
114+ function directoryRenames ( source , oldName , newName ) {
115+ const entries = [ ] ;
116+ const prefix = `${ oldName } /` ;
117+ for ( const name of source . keys ( ) ) {
118+ if ( StringPrototypeStartsWith ( name , prefix ) ) {
119+ ArrayPrototypePush ( entries , {
120+ oldName : name ,
121+ newName : newName + StringPrototypeSlice ( name , oldName . length ) ,
122+ } ) ;
123+ }
124+ }
125+ return entries ;
126+ }
127+
105128/**
106129 * A file handle over one ZIP entry. ZIP members can't be edited in place
107130 * (they're a single compressed blob), so writes accumulate in memory and are
@@ -530,28 +553,48 @@ class ZipProvider extends VirtualProvider {
530553 const oldName = normalize ( oldPath ) ;
531554 const newName = normalize ( newPath ) ;
532555 const entry = await this . #getEntry( oldName ) ;
533- if ( entry === null ) throw createENOENT ( 'rename' , oldPath ) ;
534- const content = await entry . content ( ) ;
535- await this . #source. add ( newName , content , {
536- mode : entry . mode || undefined ,
537- modified : entry . modified ,
538- method : methodOption ( entry . method ) ,
539- } ) ;
540- await this . #source. delete ( oldName ) ;
556+ const entries = entry === null ?
557+ directoryRenames ( this . #source, oldName , newName ) :
558+ [ { oldName, newName, entry } ] ;
559+ if ( entries . length === 0 ) throw createENOENT ( 'rename' , oldPath ) ;
560+ if ( oldName === newName ) return ;
561+ if ( entry === null && StringPrototypeStartsWith ( newName , `${ oldName } /` ) ) {
562+ throw createEINVAL ( 'rename' , oldPath ) ;
563+ }
564+
565+ for ( let i = 0 ; i < entries . length ; i ++ ) {
566+ const item = entries [ i ] ;
567+ item . entry ??= await this . #getEntry( item . oldName ) ;
568+ await this . #source. add (
569+ item . newName , await item . entry . content ( ) , renameOptions ( item . entry ) ) ;
570+ }
571+ for ( let i = 0 ; i < entries . length ; i ++ ) {
572+ await this . #source. delete ( entries [ i ] . oldName ) ;
573+ }
541574 }
542575 renameSync ( oldPath , newPath ) {
543576 if ( this . readonly ) throw createEROFS ( 'rename' , oldPath ) ;
544577 const oldName = normalize ( oldPath ) ;
545578 const newName = normalize ( newPath ) ;
546579 const entry = this . #getEntrySync( oldName ) ;
547- if ( entry === null ) throw createENOENT ( 'rename' , oldPath ) ;
548- const content = entry . contentSync ( ) ;
549- this . #source. addSync ( newName , content , {
550- mode : entry . mode || undefined ,
551- modified : entry . modified ,
552- method : methodOption ( entry . method ) ,
553- } ) ;
554- this . #deleteEntrySync( oldName ) ;
580+ const entries = entry === null ?
581+ directoryRenames ( this . #source, oldName , newName ) :
582+ [ { oldName, newName, entry } ] ;
583+ if ( entries . length === 0 ) throw createENOENT ( 'rename' , oldPath ) ;
584+ if ( oldName === newName ) return ;
585+ if ( entry === null && StringPrototypeStartsWith ( newName , `${ oldName } /` ) ) {
586+ throw createEINVAL ( 'rename' , oldPath ) ;
587+ }
588+
589+ for ( let i = 0 ; i < entries . length ; i ++ ) {
590+ const item = entries [ i ] ;
591+ item . entry ??= this . #getEntrySync( item . oldName ) ;
592+ this . #source. addSync (
593+ item . newName , item . entry . contentSync ( ) , renameOptions ( item . entry ) ) ;
594+ }
595+ for ( let i = 0 ; i < entries . length ; i ++ ) {
596+ this . #deleteEntrySync( entries [ i ] . oldName ) ;
597+ }
555598 }
556599
557600 /**
0 commit comments