import * as fs from 'fs'; interface IPendingWrite { lastSize: number; lastChange: number; timeoutId: ReturnType; resolve: (stats: fs.Stats) => void; reject: (error: Error) => void; } /** * Implements awaitWriteFinish functionality by polling file size until stable. * This replaces chokidar's built-in write stabilization. */ export class WriteStabilizer { private pendingWrites = new Map(); constructor( private stabilityThreshold: number = 300, private pollInterval: number = 100 ) {} /** * Wait for a file write to complete by polling until size is stable */ async waitForWriteFinish(filePath: string): Promise { // Cancel any existing pending check for this file this.cancel(filePath); return new Promise((resolve, reject) => { const poll = async () => { try { const stats = await fs.promises.stat(filePath); const pending = this.pendingWrites.get(filePath); if (!pending) { // Was cancelled return; } const now = Date.now(); if (stats.size !== pending.lastSize) { // Size changed - file is still being written, reset timer pending.lastSize = stats.size; pending.lastChange = now; pending.timeoutId = setTimeout(poll, this.pollInterval); } else if (now - pending.lastChange >= this.stabilityThreshold) { // Size has been stable for the threshold duration this.pendingWrites.delete(filePath); resolve(stats); } else { // Size is the same but not yet past threshold pending.timeoutId = setTimeout(poll, this.pollInterval); } } catch (error: any) { this.pendingWrites.delete(filePath); if (error.code === 'ENOENT') { // File was deleted during polling reject(new Error(`File was deleted: ${filePath}`)); } else { reject(error); } } }; this.pendingWrites.set(filePath, { lastSize: -1, lastChange: Date.now(), timeoutId: setTimeout(poll, this.pollInterval), resolve, reject }); }); } /** * Cancel any pending write stabilization for a file */ cancel(filePath: string): void { const pending = this.pendingWrites.get(filePath); if (pending) { clearTimeout(pending.timeoutId); this.pendingWrites.delete(filePath); } } /** * Cancel all pending write stabilizations */ cancelAll(): void { for (const [filePath, pending] of this.pendingWrites) { clearTimeout(pending.timeoutId); } this.pendingWrites.clear(); } }