diff --git a/packages/artifact/__tests__/download-artifact.test.ts b/packages/artifact/__tests__/download-artifact.test.ts index 465beccd..82c6d740 100644 --- a/packages/artifact/__tests__/download-artifact.test.ts +++ b/packages/artifact/__tests__/download-artifact.test.ts @@ -815,5 +815,31 @@ describe('download-artifact', () => { // Verify files were extracted await expectExtractedArchive(fixtures.workspaceDir) }) + + it('should skip decompression when skipDecompress option is true even for zip content-type', async () => { + const mockHttpClient = (HttpClient as jest.Mock).mockImplementation( + () => { + return { + get: mockGetArtifactSuccess + } + } + ) + + await streamExtractExternal( + fixtures.blobStorageUrl, + fixtures.workspaceDir, + {skipDecompress: true} + ) + + expect(mockHttpClient).toHaveBeenCalledWith(getUserAgentString()) + // Verify zip was saved as-is, not extracted + // When skipDecompress is true, the file should be saved with default name 'artifact' + const savedFilePath = path.join(fixtures.workspaceDir, 'artifact') + expect(fs.existsSync(savedFilePath)).toBe(true) + // The saved file should be the raw zip content + const savedContent = fs.readFileSync(savedFilePath) + const originalZipContent = fs.readFileSync(fixtures.exampleArtifact.path) + expect(savedContent).toEqual(originalZipContent) + }) }) }) diff --git a/packages/artifact/src/internal/download/download-artifact.ts b/packages/artifact/src/internal/download/download-artifact.ts index fdd22190..173c0b57 100644 --- a/packages/artifact/src/internal/download/download-artifact.ts +++ b/packages/artifact/src/internal/download/download-artifact.ts @@ -45,12 +45,13 @@ async function exists(path: string): Promise { async function streamExtract( url: string, - directory: string + directory: string, + skipDecompress?: boolean ): Promise { let retryCount = 0 while (retryCount < 5) { try { - return await streamExtractExternal(url, directory) + return await streamExtractExternal(url, directory, {skipDecompress}) } catch (error) { retryCount++ core.debug( @@ -67,8 +68,9 @@ async function streamExtract( export async function streamExtractExternal( url: string, directory: string, - opts: {timeout: number} = {timeout: 30 * 1000} + opts: {timeout?: number; skipDecompress?: boolean} = {} ): Promise { + const {timeout = 30 * 1000, skipDecompress = false} = opts const client = new httpClient.HttpClient(getUserAgentString()) const response = await client.get(url) if (response.message.statusCode !== 200) { @@ -94,7 +96,7 @@ export async function streamExtractExternal( fileName = decodeURIComponent(filenameMatch[1].trim()) } - core.debug(`Content-Type: ${contentType}, isZip: ${isZip}`) + core.debug(`Content-Type: ${contentType}, isZip: ${isZip}, skipDecompress: ${skipDecompress}`) core.debug(`Content-Disposition: ${contentDisposition}, fileName: ${fileName}`) let sha256Digest: string | undefined = undefined @@ -102,12 +104,12 @@ export async function streamExtractExternal( return new Promise((resolve, reject) => { const timerFn = (): void => { const timeoutError = new Error( - `Blob storage chunk did not respond in ${opts.timeout}ms` + `Blob storage chunk did not respond in ${timeout}ms` ) response.message.destroy(timeoutError) reject(timeoutError) } - const timer = setTimeout(timerFn, opts.timeout) + const timer = setTimeout(timerFn, timeout) const onError = (error: Error): void => { core.debug( @@ -137,7 +139,7 @@ export async function streamExtractExternal( resolve({sha256Digest: `sha256:${sha256Digest}`}) } - if (isZip) { + if (isZip && !skipDecompress) { // Extract zip file passThrough.pipe(unzip.Extract({path: directory})).on('close', onClose).on('error', onError) } else { @@ -193,7 +195,11 @@ export async function downloadArtifactPublic( try { core.info(`Starting download of artifact to: ${downloadPath}`) - const extractResponse = await streamExtract(location, downloadPath) + const extractResponse = await streamExtract( + location, + downloadPath, + options?.skipDecompress + ) core.info(`Artifact download completed successfully.`) if (options?.expectedHash) { if (options?.expectedHash !== extractResponse.sha256Digest) { @@ -254,7 +260,11 @@ export async function downloadArtifactInternal( try { core.info(`Starting download of artifact to: ${downloadPath}`) - const extractResponse = await streamExtract(signedUrl, downloadPath) + const extractResponse = await streamExtract( + signedUrl, + downloadPath, + options?.skipDecompress + ) core.info(`Artifact download completed successfully.`) if (options?.expectedHash) { if (options?.expectedHash !== extractResponse.sha256Digest) { diff --git a/packages/artifact/src/internal/shared/interfaces.ts b/packages/artifact/src/internal/shared/interfaces.ts index 9675d39a..784ff98d 100644 --- a/packages/artifact/src/internal/shared/interfaces.ts +++ b/packages/artifact/src/internal/shared/interfaces.ts @@ -113,6 +113,12 @@ export interface DownloadArtifactOptions { * matches the expected hash. */ expectedHash?: string + + /** + * If true, the downloaded artifact will not be automatically extracted/decompressed. + * The artifact will be saved as-is to the destination path. + */ + skipDecompress?: boolean } export interface StreamExtractResponse {