@@ -186,203 +186,52 @@ export class PublishManager {
186
186
}
187
187
188
188
/**
189
- * Serialize files from a directory into a record of file paths to their content
190
- * Memory-optimized version using chunking + batching approach
189
+ * Serializes all files in a directory for deployment using parallel processing
191
190
* @param currentDir - The directory path to serialize
192
191
* @returns Record of file paths to their content (base64 for binary, utf-8 for text)
193
192
*/
194
193
private async serializeFiles ( currentDir : string ) : Promise < Record < string , FreestyleFile > > {
195
194
const timer = new LogTimer ( 'File Serialization' ) ;
196
- const startTime = Date . now ( ) ;
197
195
198
196
try {
199
197
const allFilePaths = await this . getAllFilePathsFlat ( currentDir ) ;
200
198
timer . log ( `File discovery completed - ${ allFilePaths . length } files found` ) ;
201
199
202
200
const filteredPaths = allFilePaths . filter ( filePath => ! this . shouldSkipFile ( filePath ) ) ;
203
- timer . log ( `Filtered to ${ filteredPaths . length } files after exclusions` ) ;
204
201
205
202
const { binaryFiles, textFiles } = this . categorizeFiles ( filteredPaths ) ;
206
- timer . log ( `Categorized: ${ textFiles . length } text files, ${ binaryFiles . length } binary files` ) ;
207
203
204
+ const BATCH_SIZE = 10 ;
208
205
const files : Record < string , FreestyleFile > = { } ;
209
-
206
+
210
207
if ( textFiles . length > 0 ) {
211
- timer . log ( `Processing ${ textFiles . length } text files using chunking + batching` ) ;
212
- const textResults = await this . processFilesWithChunkingAndBatching ( textFiles , currentDir , false ) ;
213
- Object . assign ( files , textResults ) ;
208
+ timer . log ( `Processing ${ textFiles . length } text files in batches of ${ BATCH_SIZE } ` ) ;
209
+ for ( let i = 0 ; i < textFiles . length ; i += BATCH_SIZE ) {
210
+ const batch = textFiles . slice ( i , i + BATCH_SIZE ) ;
211
+ const batchFiles = await this . processTextFilesBatch ( batch , currentDir ) ;
212
+ Object . assign ( files , batchFiles ) ;
213
+ }
214
214
timer . log ( 'Text files processing completed' ) ;
215
215
}
216
216
217
217
if ( binaryFiles . length > 0 ) {
218
- timer . log ( `Processing ${ binaryFiles . length } binary files using chunking + batching` ) ;
219
- const binaryResults = await this . processFilesWithChunkingAndBatching ( binaryFiles , currentDir , true ) ;
220
- Object . assign ( files , binaryResults ) ;
218
+ timer . log ( `Processing ${ binaryFiles . length } binary files in batches of ${ BATCH_SIZE } ` ) ;
219
+ for ( let i = 0 ; i < binaryFiles . length ; i += BATCH_SIZE ) {
220
+ const batch = binaryFiles . slice ( i , i + BATCH_SIZE ) ;
221
+ const batchFiles = await this . processBinaryFilesBatch ( batch , currentDir ) ;
222
+ Object . assign ( files , batchFiles ) ;
223
+ }
221
224
timer . log ( 'Binary files processing completed' ) ;
222
225
}
223
226
224
- const endTime = Date . now ( ) ;
225
- const totalTime = endTime - startTime ;
226
- timer . log ( `Serialization completed - ${ Object . keys ( files ) . length } files processed in ${ totalTime } ms` ) ;
227
+ timer . log ( `Serialization completed - ${ Object . keys ( files ) . length } files processed` ) ;
227
228
return files ;
228
229
} catch ( error ) {
229
- const endTime = Date . now ( ) ;
230
- const totalTime = endTime - startTime ;
231
- console . error ( `[serializeFiles] Error during serialization after ${ totalTime } ms:` , error ) ;
230
+ console . error ( `[serializeFiles] Error during serialization:` , error ) ;
232
231
throw error ;
233
232
}
234
233
}
235
234
236
- /**
237
- * Process files using chunking + batching approach
238
- * 1. Split files into chunks
239
- * 2. Process each chunk in batches
240
- * 3. Clean up memory after each chunk
241
- */
242
- private async processFilesWithChunkingAndBatching (
243
- filePaths : string [ ] ,
244
- baseDir : string ,
245
- isBinary : boolean ,
246
- chunkSize = 100 ,
247
- batchSize = 10
248
- ) : Promise < Record < string , FreestyleFile > > {
249
- const files : Record < string , FreestyleFile > = { } ;
250
- const totalFiles = filePaths . length ;
251
- let processedCount = 0 ;
252
- const chunkStartTime = Date . now ( ) ;
253
-
254
- for ( let i = 0 ; i < filePaths . length ; i += chunkSize ) {
255
- const chunk = filePaths . slice ( i , i + chunkSize ) ;
256
- const chunkNumber = Math . floor ( i / chunkSize ) + 1 ;
257
- const totalChunks = Math . ceil ( filePaths . length / chunkSize ) ;
258
- console . log ( `Processing chunk ${ chunkNumber } /${ totalChunks } (${ chunk . length } files)` ) ;
259
-
260
- const chunkResults = await this . processChunkInBatches ( chunk , baseDir , isBinary , batchSize ) ;
261
- Object . assign ( files , chunkResults ) ;
262
-
263
- processedCount += chunk . length ;
264
- const chunkTime = Date . now ( ) - chunkStartTime ;
265
- console . log ( `Completed chunk ${ chunkNumber } /${ totalChunks } . Total processed: ${ processedCount } /${ totalFiles } (${ chunkTime } ms elapsed)` ) ;
266
-
267
- if ( global . gc ) {
268
- global . gc ( ) ;
269
- }
270
- }
271
-
272
- const totalChunkTime = Date . now ( ) - chunkStartTime ;
273
- console . log ( `Completed all chunks in ${ totalChunkTime } ms` ) ;
274
- return files ;
275
- }
276
-
277
- private async processChunkInBatches (
278
- chunk : string [ ] ,
279
- baseDir : string ,
280
- isBinary : boolean ,
281
- batchSize = 10
282
- ) : Promise < Record < string , FreestyleFile > > {
283
- const files : Record < string , FreestyleFile > = { } ;
284
- const batchStartTime = Date . now ( ) ;
285
-
286
- for ( let i = 0 ; i < chunk . length ; i += batchSize ) {
287
- const batch = chunk . slice ( i , i + batchSize ) ;
288
- const batchNumber = Math . floor ( i / batchSize ) + 1 ;
289
- const totalBatches = Math . ceil ( chunk . length / batchSize ) ;
290
-
291
- const batchPromises = batch . map ( filePath =>
292
- isBinary
293
- ? this . processBinaryFile ( filePath , baseDir )
294
- : this . processTextFile ( filePath , baseDir )
295
- ) ;
296
-
297
- const batchResults = await Promise . all ( batchPromises ) ;
298
-
299
- // Add successful results to files
300
- for ( const result of batchResults ) {
301
- if ( result ) {
302
- files [ result . path ] = result . file ;
303
- }
304
- }
305
-
306
- const batchTime = Date . now ( ) - batchStartTime ;
307
- console . log ( ` Batch ${ batchNumber } /${ totalBatches } completed (${ batch . length } files) in ${ batchTime } ms` ) ;
308
- }
309
-
310
- const totalBatchTime = Date . now ( ) - batchStartTime ;
311
- console . log ( ` All batches completed in ${ totalBatchTime } ms` ) ;
312
- return files ;
313
- }
314
-
315
- private async processTextFile ( fullPath : string , baseDir : string ) : Promise < { path : string ; file : FreestyleFile } | null > {
316
- const relativePath = fullPath . replace ( baseDir + '/' , '' ) ;
317
-
318
- try {
319
- const textContent = await this . session . fs . readTextFile ( fullPath ) ;
320
-
321
- if ( textContent !== null ) {
322
- // Skip very large text files to prevent memory issues
323
- const MAX_TEXT_SIZE = 5 * 1024 * 1024 ; // 5MB limit for text files
324
- if ( textContent . length > MAX_TEXT_SIZE ) {
325
- console . warn ( `[processTextFile] Skipping large text file ${ relativePath } (${ textContent . length } bytes)` ) ;
326
- return null ;
327
- }
328
-
329
- return {
330
- path : relativePath ,
331
- file : {
332
- content : textContent ,
333
- encoding : 'utf-8' as const ,
334
- }
335
- } ;
336
- } else {
337
- console . warn ( `[processTextFile] Failed to read text content for ${ relativePath } ` ) ;
338
- return null ;
339
- }
340
- } catch ( error ) {
341
- console . warn ( `[processTextFile] Error processing ${ relativePath } :` , error ) ;
342
- return null ;
343
- }
344
- }
345
-
346
-
347
- private async processBinaryFile ( fullPath : string , baseDir : string ) : Promise < { path : string ; file : FreestyleFile } | null > {
348
- const relativePath = fullPath . replace ( baseDir + '/' , '' ) ;
349
-
350
- try {
351
- const binaryContent = await this . session . fs . readFile ( fullPath ) ;
352
-
353
- if ( binaryContent ) {
354
- // For very large binary files, consider skipping or compressing ??
355
- const MAX_BINARY_SIZE = 10 * 1024 * 1024 ; // 10MB limit
356
- if ( binaryContent . length > MAX_BINARY_SIZE ) {
357
- console . warn ( `[processBinaryFile] Skipping large binary file ${ relativePath } (${ binaryContent . length } bytes)` ) ;
358
- return null ;
359
- }
360
-
361
- const base64String = convertToBase64 ( binaryContent ) ;
362
-
363
- return {
364
- path : relativePath ,
365
- file : {
366
- content : base64String ,
367
- encoding : 'base64' as const ,
368
- }
369
- } ;
370
- } else {
371
- console . warn ( `[processBinaryFile] Failed to read binary content for ${ relativePath } ` ) ;
372
- return null ;
373
- }
374
- } catch ( error ) {
375
- console . warn ( `[processBinaryFile] Error processing ${ relativePath } :` , error ) ;
376
- return null ;
377
- }
378
- }
379
-
380
-
381
-
382
- /**
383
- * Get all file paths in a directory tree using memory-efficient streaming
384
- * Instead of accumulating all paths in memory, we yield them one by one
385
- */
386
235
private async getAllFilePathsFlat ( rootDir : string ) : Promise < string [ ] > {
387
236
const allPaths : string [ ] = [ ] ;
388
237
const dirsToProcess = [ rootDir ] ;
@@ -439,4 +288,81 @@ export class PublishManager {
439
288
440
289
return { binaryFiles, textFiles } ;
441
290
}
291
+
292
+
293
+ private async processTextFilesBatch ( filePaths : string [ ] , baseDir : string ) : Promise < Record < string , FreestyleFile > > {
294
+ const promises = filePaths . map ( async ( fullPath ) => {
295
+ const relativePath = fullPath . replace ( baseDir + '/' , '' ) ;
296
+
297
+ try {
298
+ const textContent = await this . session . fs . readTextFile ( fullPath ) ;
299
+
300
+ if ( textContent !== null ) {
301
+ return {
302
+ path : relativePath ,
303
+ file : {
304
+ content : textContent ,
305
+ encoding : 'utf-8' as const ,
306
+ }
307
+ } ;
308
+ } else {
309
+ console . warn ( `[processTextFilesBatch] Failed to read text content for ${ relativePath } ` ) ;
310
+ return null ;
311
+ }
312
+ } catch ( error ) {
313
+ console . warn ( `[processTextFilesBatch] Error processing ${ relativePath } :` , error ) ;
314
+ return null ;
315
+ }
316
+ } ) ;
317
+
318
+ const results = await Promise . all ( promises ) ;
319
+ const files : Record < string , FreestyleFile > = { } ;
320
+
321
+ for ( const result of results ) {
322
+ if ( result ) {
323
+ files [ result . path ] = result . file ;
324
+ }
325
+ }
326
+
327
+ return files ;
328
+ }
329
+
330
+ private async processBinaryFilesBatch ( filePaths : string [ ] , baseDir : string ) : Promise < Record < string , FreestyleFile > > {
331
+ const promises = filePaths . map ( async ( fullPath ) => {
332
+ const relativePath = fullPath . replace ( baseDir + '/' , '' ) ;
333
+
334
+ try {
335
+ const binaryContent = await this . session . fs . readFile ( fullPath ) ;
336
+
337
+ if ( binaryContent ) {
338
+ const base64String = convertToBase64 ( binaryContent ) ;
339
+
340
+ return {
341
+ path : relativePath ,
342
+ file : {
343
+ content : base64String ,
344
+ encoding : 'base64' as const ,
345
+ }
346
+ } ;
347
+ } else {
348
+ console . warn ( `[processBinaryFilesBatch] Failed to read binary content for ${ relativePath } ` ) ;
349
+ return null ;
350
+ }
351
+ } catch ( error ) {
352
+ console . warn ( `[processBinaryFilesBatch] Error processing ${ relativePath } :` , error ) ;
353
+ return null ;
354
+ }
355
+ } ) ;
356
+
357
+ const results = await Promise . all ( promises ) ;
358
+ const files : Record < string , FreestyleFile > = { } ;
359
+
360
+ for ( const result of results ) {
361
+ if ( result ) {
362
+ files [ result . path ] = result . file ;
363
+ }
364
+ }
365
+
366
+ return files ;
367
+ }
442
368
}
0 commit comments