@@ -176,36 +176,35 @@ export default async function installPlugin(
176176 // Track unsafe absolute entries to skip
177177 const ignoredUnsafeEntries = new Set ( ) ;
178178
179- const promises = Object . keys ( zip . files ) . map ( async ( file ) => {
179+ const files = Object . keys ( zip . files ) ;
180+ const limit = 2 ;
181+
182+ async function processFile ( file ) {
180183 try {
181- let correctFile = file ;
182- if ( / \\ / . test ( correctFile ) ) {
183- correctFile = correctFile . replace ( / \\ / g, "/" ) ;
184- }
184+ const entry = zip . files [ file ] ;
185185
186- // Determine if the zip entry is a directory from JSZip metadata
187- const isDirEntry = ! ! zip . files [ file ] . dir || / \/ $ / . test ( correctFile ) ;
186+ let correctFile = file . replace ( / \\ / g , "/" ) ;
187+ const isDirEntry = entry . dir || correctFile . endsWith ( "/" ) ;
188188
189- // If the original path is absolute or otherwise unsafe, skip it and warn later
190189 if ( isUnsafeAbsolutePath ( file ) ) {
191190 ignoredUnsafeEntries . add ( file ) ;
192191 return ;
193192 }
194193
195- // Sanitize path so it cannot escape pluginDir or start with '/'
196194 correctFile = sanitizeZipPath ( correctFile , isDirEntry ) ;
197- if ( ! correctFile ) return ; // nothing to do
195+ if ( ! correctFile ) return ;
196+
198197 const fileUrl = Url . join ( pluginDir , correctFile ) ;
199198
200- // Always ensure directories exist for dir entries
199+ // Handle directory entries
201200 if ( isDirEntry ) {
202201 await createFileRecursive ( pluginDir , correctFile , true ) ;
203202 return ;
204203 }
205204
206- // For files, ensure parent directory exists even if state claims it exists
205+ // Ensure parent directory exists
207206 const lastSlash = correctFile . lastIndexOf ( "/" ) ;
208- if ( lastSlash >= 0 ) {
207+ if ( lastSlash !== - 1 ) {
209208 const parentRel = correctFile . slice ( 0 , lastSlash + 1 ) ;
210209 await createFileRecursive ( pluginDir , parentRel , true ) ;
211210 }
@@ -214,23 +213,28 @@ export default async function installPlugin(
214213 await createFileRecursive ( pluginDir , correctFile , false ) ;
215214 }
216215
217- let data = await zip . files [ file ] . async ( "ArrayBuffer" ) ;
216+ let data = await entry . async ( "ArrayBuffer" ) ;
218217
219218 if ( file === "plugin.json" ) {
220219 data = JSON . stringify ( pluginJson ) ;
221220 }
222221
223222 if ( ! ( await state . isUpdated ( correctFile , data ) ) ) return ;
223+
224224 await fsOperation ( fileUrl ) . writeFile ( data ) ;
225- return ;
226225 } catch ( error ) {
227226 console . error ( `Error processing file ${ file } :` , error ) ;
228227 }
229- } ) ;
228+ }
230229
231- // Wait for all files to be processed
232- await Promise . allSettled ( promises ) ;
230+ // Process in batches
231+ for ( let i = 0 ; i < files . length ; i += limit ) {
232+ const batch = files . slice ( i , i + limit ) ;
233+ await Promise . allSettled ( batch . map ( processFile ) ) ;
233234
235+ // Allow UI thread to breathe
236+ await new Promise ( ( r ) => setTimeout ( r , 0 ) ) ;
237+ }
234238 // Emit a non-blocking warning if any unsafe entries were skipped
235239 if ( ! isDependency && ignoredUnsafeEntries . size ) {
236240 const sample = Array . from ( ignoredUnsafeEntries ) . slice ( 0 , 3 ) . join ( ", " ) ;
0 commit comments