11import { once } from 'node:events'
22import { createWriteStream , type WriteStream } from 'node:fs'
3+ import { link , mkdtemp , rename , rm } from 'node:fs/promises'
4+ import { dirname , join } from 'node:path'
35import { Readable , type Writable } from 'node:stream'
46import { pipeline } from 'node:stream/promises'
57import type { Command } from 'commander'
@@ -8,22 +10,50 @@ import { V2_OPERATIONS } from '../../generated/v2-api'
810import { resolvePath , SimApiError } from '../../http/client'
911import { printProtocolResult } from './result'
1012
13+ function writeFailure ( path : WriteStream [ 'path' ] , error : unknown ) : SimApiError {
14+ const code = ( error as NodeJS . ErrnoException ) . code
15+ if ( code === 'EEXIST' ) {
16+ return new SimApiError (
17+ `${ path } already exists. Pass --force to overwrite it, or choose another output path.` ,
18+ 0
19+ )
20+ }
21+ return new SimApiError ( `Could not write ${ path } : ${ ( error as Error ) . message } ` , 0 )
22+ }
23+
1124/** Streams a fetch body to disk while honoring write-stream backpressure. */
1225export async function streamToFile (
1326 body : ReadableStream < Uint8Array > ,
14- file : Writable & Pick < WriteStream , 'path' >
27+ file : Writable & Pick < WriteStream , 'path' > ,
28+ reportedPath : WriteStream [ 'path' ] = file . path
1529) : Promise < void > {
1630 try {
1731 await pipeline ( Readable . fromWeb ( body as Parameters < typeof Readable . fromWeb > [ 0 ] ) , file )
1832 } catch ( error ) {
19- const code = ( error as NodeJS . ErrnoException ) . code
20- if ( code === 'EEXIST' ) {
21- throw new SimApiError (
22- `${ file . path } already exists. Pass --force to overwrite it, or choose another output path.` ,
23- 0
24- )
25- }
26- throw new SimApiError ( `Could not write ${ file . path } : ${ ( error as Error ) . message } ` , 0 )
33+ throw writeFailure ( reportedPath , error )
34+ }
35+ }
36+
37+ /** Stages a complete download beside its destination before publishing it. */
38+ export async function saveToFile (
39+ body : ReadableStream < Uint8Array > ,
40+ target : string ,
41+ force : boolean
42+ ) : Promise < void > {
43+ let temporaryDirectory : string | null = null
44+
45+ try {
46+ temporaryDirectory = await mkdtemp ( join ( dirname ( target ) , '.sim-download-' ) )
47+ const temporaryPath = join ( temporaryDirectory , 'payload' )
48+ await streamToFile ( body , createWriteStream ( temporaryPath , { flags : 'wx' } ) , target )
49+
50+ if ( force ) await rename ( temporaryPath , target )
51+ else await link ( temporaryPath , target )
52+ } catch ( error ) {
53+ if ( error instanceof SimApiError ) throw error
54+ throw writeFailure ( target , error )
55+ } finally {
56+ if ( temporaryDirectory ) await rm ( temporaryDirectory , { recursive : true , force : true } )
2757 }
2858}
2959
@@ -111,10 +141,7 @@ export function attachFileGet(files: Command): void {
111141
112142 const target = options . outputFile
113143
114- await streamToFile (
115- response . body ,
116- createWriteStream ( target , { flags : options . force ? 'w' : 'wx' } )
117- )
144+ await saveToFile ( response . body , target , Boolean ( options . force ) )
118145 printProtocolResult ( profile . output , {
119146 id : fileId ,
120147 path : target ,
0 commit comments