Add support for specifying whether to skip decompressing

This commit is contained in:
Daniel Kennedy
2026-01-26 14:35:09 -05:00
parent c2139ac002
commit e7359c0031
3 changed files with 51 additions and 9 deletions
@@ -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)
})
})
})
@@ -45,12 +45,13 @@ async function exists(path: string): Promise<boolean> {
async function streamExtract(
url: string,
directory: string
directory: string,
skipDecompress?: boolean
): Promise<StreamExtractResponse> {
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<StreamExtractResponse> {
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) {
@@ -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 {