What do you actually get from stream.pipeline that chaining .pipe() calls doesn't give you?
A strong answer knows exactly where pipe falls down on errors and cleanup, which streams stay open, what leaks, and what pipeline destroys when
So pipe() moves the data and handles backpressure fine, but the error handling is where it falls down. Errors don't propagate through the chain, so each stream needs its own 'error' listener, and if one thing fails the other streams in the chain can just stay open. stream.pipeline() is the fix for that. You pass it the streams plus a callback, or there's a promise version in stream/promises, and it wires up the pipes, forwards the first error to your callback, and cleans up every stream when the pipeline finishes or fails. That cleanup is what stops the resource leaks bare pipe chains are kind of famous for, like file descriptors left open when a destination errors. The rule I've picked up is basically use pipeline for anything production-facing, and treat pipe as the lower-level primitive it wraps.