(
tempDir: string,
url: string,
headers?: Record<string, string>,
options?: { signal?: AbortSignal; downloadTimeoutMs?: number },
)
| 113 | |
| 114 | // fallow-ignore-next-line complexity |
| 115 | async function downloadToTempFile( |
| 116 | tempDir: string, |
| 117 | url: string, |
| 118 | headers?: Record<string, string>, |
| 119 | options?: { signal?: AbortSignal; downloadTimeoutMs?: number }, |
| 120 | ): Promise<string> { |
| 121 | let parsedUrl: URL; |
| 122 | try { |
| 123 | parsedUrl = new URL(url); |
| 124 | } catch { |
| 125 | throw new AppError('INVALID_ARGS', `Invalid source URL: ${url}`); |
| 126 | } |
| 127 | await validateDownloadSourceUrl(parsedUrl); |
| 128 | const requestSignal = options?.signal; |
| 129 | if (requestSignal?.aborted) { |
| 130 | throw new AppError('COMMAND_FAILED', 'request canceled', { reason: 'request_canceled' }); |
| 131 | } |
| 132 | const timeoutMs = options?.downloadTimeoutMs ?? DEFAULT_SOURCE_DOWNLOAD_TIMEOUT_MS; |
| 133 | const timeoutSignal = AbortSignal.timeout(timeoutMs); |
| 134 | const signal = requestSignal ? AbortSignal.any([requestSignal, timeoutSignal]) : timeoutSignal; |
| 135 | try { |
| 136 | const response = await fetch(parsedUrl, { |
| 137 | headers, |
| 138 | redirect: 'follow', |
| 139 | signal, |
| 140 | }); |
| 141 | if (!response.ok) { |
| 142 | throw new AppError( |
| 143 | 'COMMAND_FAILED', |
| 144 | `Failed to download app source: ${response.status} ${response.statusText}`, |
| 145 | { |
| 146 | status: response.status, |
| 147 | statusText: response.statusText, |
| 148 | url: parsedUrl.toString(), |
| 149 | }, |
| 150 | ); |
| 151 | } |
| 152 | const downloadName = resolveDownloadFileName(response, parsedUrl); |
| 153 | const destinationPath = path.join(tempDir, downloadName); |
| 154 | const body = response.body; |
| 155 | if (!body) { |
| 156 | throw new AppError('COMMAND_FAILED', 'Download response body was empty', { |
| 157 | url: parsedUrl.toString(), |
| 158 | }); |
| 159 | } |
| 160 | await pipeline( |
| 161 | Readable.fromWeb(body as Parameters<typeof Readable.fromWeb>[0]), |
| 162 | createWriteStream(destinationPath), |
| 163 | ); |
| 164 | return destinationPath; |
| 165 | } catch (error) { |
| 166 | if (requestSignal?.aborted) { |
| 167 | throw new AppError( |
| 168 | 'COMMAND_FAILED', |
| 169 | 'request canceled', |
| 170 | { reason: 'request_canceled' }, |
| 171 | error, |
| 172 | ); |
no test coverage detected