* Open the `--out` target before any network I/O so a permission/dir * error fails fast. Synchronous open via `createWriteStream` doesn't * actually open the descriptor until first write, so we don't surface * EACCES/ENOENT here — instead the stream emits `'error'`, which we * remember on the si
(rawPath: string)
| 7764 | * on a sandboxed fs, etc.) are caught before the API request goes out. |
| 7765 | */ |
| 7766 | function openOutputFile(rawPath: string): FileSink { |
| 7767 | if (typeof rawPath !== 'string' || rawPath.length === 0) { |
| 7768 | throw localValidationError('out', 'must be a non-empty file path'); |
| 7769 | } |
| 7770 | const resolved = isAbsolute(rawPath) ? rawPath : resolve(process.cwd(), rawPath); |
| 7771 | // Defensive: reject obviously-bad paths up front (a directory string |
| 7772 | // would fail later with EISDIR; that's a clearer 5/VALIDATION_ERROR |
| 7773 | // surface than letting it crash mid-write with TransportError). |
| 7774 | if (resolved.endsWith('/')) { |
| 7775 | throw localValidationError('out', 'must point to a file, not a directory'); |
| 7776 | } |
| 7777 | // Validate the parent dir synchronously so a missing or non-directory |
| 7778 | // parent surfaces as exit 5 / VALIDATION_ERROR rather than exit 1 / |
| 7779 | // TRANSPORT_ERROR. Without this, an ENOENT/ENOTDIR fires asynchronously |
| 7780 | // on first write and gets re-raised through `closeOutputFile` as a |
| 7781 | // TransportError — an exit-code mismatch with the rest of `--out`'s |
| 7782 | // input validation. |
| 7783 | const parent = dirname(resolved); |
| 7784 | let parentStat; |
| 7785 | try { |
| 7786 | parentStat = statSync(parent); |
| 7787 | } catch { |
| 7788 | throw localValidationError('out', `parent directory does not exist: ${parent}`); |
| 7789 | } |
| 7790 | if (!parentStat.isDirectory()) { |
| 7791 | throw localValidationError('out', `parent path is not a directory: ${parent}`); |
| 7792 | } |
| 7793 | const stream = createWriteStream(resolved, { encoding: 'utf8' }); |
| 7794 | const sink: FileSink = { stream, path: resolved, error: null }; |
| 7795 | stream.on('error', err => { |
| 7796 | sink.error = err instanceof Error ? err : new Error(String(err)); |
| 7797 | }); |
| 7798 | return sink; |
| 7799 | } |
| 7800 | |
| 7801 | /** |
| 7802 | * Adapter that turns a `FileSink` into the `Output` writer set. Both |
no test coverage detected