diff --git a/packages/cli-kit/src/public/node/github.test.ts b/packages/cli-kit/src/public/node/github.test.ts index d02f427c6e..fc8b862247 100644 --- a/packages/cli-kit/src/public/node/github.test.ts +++ b/packages/cli-kit/src/public/node/github.test.ts @@ -13,6 +13,7 @@ import {readFile} from './fs.js' import {isExecutable} from 'is-executable' import {describe, expect, test, vi} from 'vitest' import {Response} from 'node-fetch' +import {Readable} from 'stream' vi.mock('./http.js') @@ -180,10 +181,10 @@ describe('downloadGitHubRelease', () => { testWithTempDir('successfully downloads the release asset', async ({tempDir}) => { // GIVEN const downloadContent = 'hello' - const content = Buffer.from(downloadContent) + const content = Readable.from(downloadContent) const mockResponse = { ok: true, - arrayBuffer: vi.fn().mockResolvedValue(content), + body: content, } vi.mocked(fetch).mockResolvedValue(mockResponse as any) @@ -221,7 +222,25 @@ describe('downloadGitHubRelease', () => { testWithTempDir('throws an AbortError when the response is not ok', async ({tempDir}) => { // GIVEN - vi.mocked(downloadFile).mockRejectedValue(new Error('Not Found')) + vi.mocked(fetch).mockResolvedValue({ + ok: false, + statusText: 'Not Found', + } as any) + const targetPath = joinPath(tempDir, 'downloads', 'example') + + // WHEN + const result = downloadGitHubRelease(repo, version, asset, targetPath) + + // THEN + await expect(result).rejects.toThrow(AbortError) + }) + + testWithTempDir('throws an AbortError when the response body is missing', async ({tempDir}) => { + // GIVEN + vi.mocked(fetch).mockResolvedValue({ + ok: true, + body: null, + } as any) const targetPath = joinPath(tempDir, 'downloads', 'example') // WHEN diff --git a/packages/cli-kit/src/public/node/github.ts b/packages/cli-kit/src/public/node/github.ts index 0018b744da..84e9f5e4e0 100644 --- a/packages/cli-kit/src/public/node/github.ts +++ b/packages/cli-kit/src/public/node/github.ts @@ -1,10 +1,11 @@ import {outputContent, outputDebug, outputToken} from './output.js' import {err, ok, Result} from './result.js' import {fetch, Response} from './http.js' -import {writeFile, mkdir, inTemporaryDirectory, moveFile, chmod} from './fs.js' +import {mkdir, inTemporaryDirectory, moveFile, chmod, createFileWriteStream} from './fs.js' import {dirname, joinPath} from './path.js' import {runWithTimer} from './metadata.js' import {AbortError} from './error.js' +import {pipeline} from 'stream/promises' class GitHubClientError extends Error { // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -162,8 +163,10 @@ export async function downloadGitHubRelease( ) } - const buffer = await response.arrayBuffer() - await writeFile(tempPath, Buffer.from(buffer)) + if (!response.body) { + throw new AbortError(`Failed to download ${assetName}: No response body`) + } + await pipeline(response.body, createFileWriteStream(tempPath)) await chmod(tempPath, 0o755) await mkdir(dirname(targetPath))