output-stream.ts 1.2 KB

1234567891011121314151617181920212223242526272829
  1. /** Bounded drainage for raw process output after managed execution ends. */
  2. import type { Readable } from 'node:stream'
  3. /**
  4. * Wait for queued bytes without letting an inherited descriptor retain a run forever.
  5. * @param stream - Caller-owned raw process output, when provided.
  6. * @param graceMs - Maximum wait after managed process termination.
  7. * @returns Whether the complete stream ended without a transport error.
  8. */
  9. export function drainOutput(stream: Readable | undefined, graceMs: number): Promise<boolean> {
  10. if (stream === undefined || stream.readableEnded) return Promise.resolve(true)
  11. if (stream.destroyed) return Promise.resolve(false)
  12. return new Promise((resolve) => {
  13. const finish = (complete: boolean): void => {
  14. clearTimeout(timer)
  15. stream.off('end', onEnd)
  16. stream.off('close', onClose)
  17. stream.off('error', onError)
  18. resolve(complete)
  19. }
  20. const onEnd = (): void => { finish(true) }
  21. const onClose = (): void => { finish(false) }
  22. const onError = (): void => { finish(false) }
  23. const timer = setTimeout(() => { finish(false) }, graceMs)
  24. stream.once('end', onEnd)
  25. stream.once('close', onClose)
  26. stream.once('error', onError)
  27. })
  28. }