11import { relativePath , joinPath , dirname } from './path.js'
2- import { glob , removeFile } from './fs.js'
2+ import { glob , removeFile , readFile } from './fs.js'
33import { outputDebug , outputContent , outputToken } from '../../public/node/output.js'
44import archiver from 'archiver'
55import { createWriteStream , readFileSync , writeFileSync } from 'fs'
@@ -64,13 +64,22 @@ export async function zip(options: ZipOptions): Promise<void> {
6464 archive . append ( Buffer . alloc ( 0 ) , { name : dirName } )
6565 }
6666
67- for ( const filePath of pathsToZip ) {
68- const fileRelativePath = relativePath ( inputDirectory , filePath )
69- if ( filePath && fileRelativePath ) archive . file ( filePath , { name : fileRelativePath } )
70- }
67+ // Read all files immediately before adding to archive to prevent ENOENT errors
68+ // Using archive.file() causes lazy loading which fails if files are deleted before finalize()
69+ const addFilesPromises = pathsToZip . map ( async ( filePath ) => {
70+ await archiveFile ( inputDirectory , filePath , archive )
71+ } )
7172
72- // eslint-disable-next-line @typescript-eslint/no-floating-promises
73- archive . finalize ( )
73+ // Wait for all files to be read and added before finalizing
74+ Promise . all ( addFilesPromises )
75+ . then ( ( ) => {
76+ // eslint-disable-next-line @typescript-eslint/no-floating-promises
77+ archive . finalize ( )
78+ } )
79+ . catch ( ( error ) => {
80+ // eslint-disable-next-line @typescript-eslint/prefer-promise-reject-errors
81+ reject ( error )
82+ } )
7483 } )
7584}
7685
@@ -84,6 +93,16 @@ function collectParentDirectories(fileRelativePath: string, accumulator: Set<str
8493 }
8594}
8695
96+ async function archiveFile ( inputDirectory : string , filePath : string , archive : archiver . Archiver ) : Promise < void > {
97+ const fileRelativePath = relativePath ( inputDirectory , filePath )
98+ if ( ! filePath || ! fileRelativePath ) return
99+
100+ // Read file content immediately to avoid race conditions
101+ const fileContent = await readFile ( filePath )
102+ // Use append with Buffer instead of file() to avoid lazy file reading
103+ archive . append ( fileContent , { name : fileRelativePath } )
104+ }
105+
87106export interface BrotliOptions {
88107 /**
89108 * The directory to compress.
@@ -147,15 +166,19 @@ export async function brotliCompress(options: BrotliOptions): Promise<void> {
147166 dot : true ,
148167 followSymbolicLinks : false ,
149168 } )
150- . then ( ( pathsToZip ) => {
151- for ( const filePath of pathsToZip ) {
152- const fileRelativePath = relativePath ( options . inputDirectory , filePath )
153- archive . file ( filePath , { name : fileRelativePath } )
154- }
169+ . then ( async ( pathsToZip ) => {
170+ // Read all files immediately to prevent ENOENT errors during race conditions
171+ const addFilesPromises = pathsToZip . map ( async ( filePath ) => {
172+ await archiveFile ( options . inputDirectory , filePath , archive )
173+ } )
174+
175+ await Promise . all ( addFilesPromises )
155176 // eslint-disable-next-line @typescript-eslint/no-floating-promises
156177 archive . finalize ( )
157178 } )
158- . catch ( ( error ) => reject ( error instanceof Error ? error : new Error ( String ( error ) ) ) )
179+ . catch ( ( error ) => {
180+ reject ( error instanceof Error ? error : new Error ( String ( error ) ) )
181+ } )
159182 } )
160183
161184 const tarContent = readFileSync ( tempTarPath )
0 commit comments