From 5191018d792457a41b504cfd06dc522a8bd64c60 Mon Sep 17 00:00:00 2001 From: Salman Chishti Date: Mon, 24 Mar 2025 06:32:22 -0700 Subject: [PATCH 01/16] Update folder structure for cache --- packages/cache/src/cache.ts | 2 +- .../cache/src/internal/cacheHttpClient.ts | 8 +- .../src/internal/shared/cacheTwirpClient.ts | 4 +- .../src/internal/{ => shared}/cacheUtils.ts | 2 +- .../internal/{ => shared}/downloadUtils.ts | 4 +- .../src/internal/{ => shared}/requestUtils.ts | 81 ++++++++++++++++++- .../src/internal/{ => shared}/uploadUtils.ts | 4 +- packages/cache/src/internal/shared/util.ts | 76 ----------------- packages/cache/src/internal/tar.ts | 2 +- 9 files changed, 92 insertions(+), 91 deletions(-) rename packages/cache/src/internal/{ => shared}/cacheUtils.ts (99%) rename packages/cache/src/internal/{ => shared}/downloadUtils.ts (99%) rename packages/cache/src/internal/{ => shared}/requestUtils.ts (56%) rename packages/cache/src/internal/{ => shared}/uploadUtils.ts (97%) delete mode 100644 packages/cache/src/internal/shared/util.ts diff --git a/packages/cache/src/cache.ts b/packages/cache/src/cache.ts index f7b2d1937e..6f8880a8b5 100644 --- a/packages/cache/src/cache.ts +++ b/packages/cache/src/cache.ts @@ -1,6 +1,6 @@ import * as core from '@actions/core' import * as path from 'path' -import * as utils from './internal/cacheUtils' +import * as utils from './internal/shared/cacheUtils' import * as cacheHttpClient from './internal/cacheHttpClient' import * as cacheTwirpClient from './internal/shared/cacheTwirpClient' import {getCacheServiceVersion, isGhes} from './internal/config' diff --git a/packages/cache/src/internal/cacheHttpClient.ts b/packages/cache/src/internal/cacheHttpClient.ts index 2470555bb1..2821acaef7 100644 --- a/packages/cache/src/internal/cacheHttpClient.ts +++ b/packages/cache/src/internal/cacheHttpClient.ts @@ -7,8 +7,8 @@ import { } from '@actions/http-client/lib/interfaces' import * as fs from 'fs' import {URL} from 'url' -import * as utils from './cacheUtils' -import {uploadCacheArchiveSDK} from './uploadUtils' +import * as utils from './shared/cacheUtils' +import {uploadCacheArchiveSDK} from './shared/uploadUtils' import { ArtifactCacheEntry, InternalCacheOptions, @@ -22,7 +22,7 @@ import { downloadCacheHttpClient, downloadCacheHttpClientConcurrent, downloadCacheStorageSDK -} from './downloadUtils' +} from './shared/downloadUtils' import { DownloadOptions, UploadOptions, @@ -33,7 +33,7 @@ import { isSuccessStatusCode, retryHttpClientResponse, retryTypedResponse -} from './requestUtils' +} from './shared/requestUtils' import {getCacheServiceURL} from './config' import {getUserAgentString} from './shared/user-agent' diff --git a/packages/cache/src/internal/shared/cacheTwirpClient.ts b/packages/cache/src/internal/shared/cacheTwirpClient.ts index f6c2af61b3..c662b43cb8 100644 --- a/packages/cache/src/internal/shared/cacheTwirpClient.ts +++ b/packages/cache/src/internal/shared/cacheTwirpClient.ts @@ -2,11 +2,11 @@ import {info, debug} from '@actions/core' import {getUserAgentString} from './user-agent' import {NetworkError, UsageError} from './errors' import {getCacheServiceURL} from '../config' -import {getRuntimeToken} from '../cacheUtils' +import {getRuntimeToken} from './cacheUtils' import {BearerCredentialHandler} from '@actions/http-client/lib/auth' import {HttpClient, HttpClientResponse, HttpCodes} from '@actions/http-client' import {CacheServiceClientJSON} from '../../generated/results/api/v1/cache.twirp-client' -import {maskSecretUrls} from './util' +import {maskSecretUrls} from './requestUtils' // The twirp http client must implement this interface interface Rpc { diff --git a/packages/cache/src/internal/cacheUtils.ts b/packages/cache/src/internal/shared/cacheUtils.ts similarity index 99% rename from packages/cache/src/internal/cacheUtils.ts rename to packages/cache/src/internal/shared/cacheUtils.ts index de9053eae0..12adc1b587 100644 --- a/packages/cache/src/internal/cacheUtils.ts +++ b/packages/cache/src/internal/shared/cacheUtils.ts @@ -11,7 +11,7 @@ import { CacheFilename, CompressionMethod, GnuTarPathOnWindows -} from './constants' +} from '../constants' const versionSalt = '1.0' diff --git a/packages/cache/src/internal/downloadUtils.ts b/packages/cache/src/internal/shared/downloadUtils.ts similarity index 99% rename from packages/cache/src/internal/downloadUtils.ts rename to packages/cache/src/internal/shared/downloadUtils.ts index de57ed78ff..145ab26866 100644 --- a/packages/cache/src/internal/downloadUtils.ts +++ b/packages/cache/src/internal/shared/downloadUtils.ts @@ -8,8 +8,8 @@ import * as stream from 'stream' import * as util from 'util' import * as utils from './cacheUtils' -import {SocketTimeout} from './constants' -import {DownloadOptions} from '../options' +import {SocketTimeout} from '../constants' +import {DownloadOptions} from '../../options' import {retryHttpClientResponse} from './requestUtils' import {AbortController} from '@azure/abort-controller' diff --git a/packages/cache/src/internal/requestUtils.ts b/packages/cache/src/internal/shared/requestUtils.ts similarity index 56% rename from packages/cache/src/internal/requestUtils.ts rename to packages/cache/src/internal/shared/requestUtils.ts index 043c8a7cef..2b1bb031a5 100644 --- a/packages/cache/src/internal/requestUtils.ts +++ b/packages/cache/src/internal/shared/requestUtils.ts @@ -4,8 +4,10 @@ import { HttpClientError, HttpClientResponse } from '@actions/http-client' -import {DefaultRetryDelay, DefaultRetryAttempts} from './constants' -import {ITypedResponseWithError} from './contracts' +import {DefaultRetryDelay, DefaultRetryAttempts} from '../constants' +import {ITypedResponseWithError} from '../contracts' + +import {debug, setSecret} from '@actions/core' export function isSuccessStatusCode(statusCode?: number): boolean { if (!statusCode) { @@ -136,3 +138,78 @@ export async function retryHttpClientResponse( delay ) } + +/** + * Masks the `sig` parameter in a URL and sets it as a secret. + * + * @param url - The URL containing the signature parameter to mask + * @remarks + * This function attempts to parse the provided URL and identify the 'sig' query parameter. + * If found, it registers both the raw and URL-encoded signature values as secrets using + * the Actions `setSecret` API, which prevents them from being displayed in logs. + * + * The function handles errors gracefully if URL parsing fails, logging them as debug messages. + * + * @example + * ```typescript + * // Mask a signature in an Azure SAS token URL + * maskSigUrl('https://example.blob.core.windows.net/container/file.txt?sig=abc123&se=2023-01-01'); + * ``` + */ +export function maskSigUrl(url: string): void { + if (!url) return + try { + const parsedUrl = new URL(url) + const signature = parsedUrl.searchParams.get('sig') + if (signature) { + setSecret(signature) + setSecret(encodeURIComponent(signature)) + } + } catch (error) { + debug( + `Failed to parse URL: ${url} ${ + error instanceof Error ? error.message : String(error) + }` + ) + } +} + +/** + * Masks sensitive information in URLs containing signature parameters. + * Currently supports masking 'sig' parameters in the 'signed_upload_url' + * and 'signed_download_url' properties of the provided object. + * + * @param body - The object should contain a signature + * @remarks + * This function extracts URLs from the object properties and calls maskSigUrl + * on each one to redact sensitive signature information. The function doesn't + * modify the original object; it only marks the signatures as secrets for + * logging purposes. + * + * @example + * ```typescript + * const responseBody = { + * signed_upload_url: 'https://blob.core.windows.net/?sig=abc123', + * signed_download_url: 'https://blob.core/windows.net/?sig=def456' + * }; + * maskSecretUrls(responseBody); + * ``` + */ +export function maskSecretUrls(body: Record | null): void { + if (typeof body !== 'object' || body === null) { + debug('body is not an object or is null') + return + } + if ( + 'signed_upload_url' in body && + typeof body.signed_upload_url === 'string' + ) { + maskSigUrl(body.signed_upload_url) + } + if ( + 'signed_download_url' in body && + typeof body.signed_download_url === 'string' + ) { + maskSigUrl(body.signed_download_url) + } +} diff --git a/packages/cache/src/internal/uploadUtils.ts b/packages/cache/src/internal/shared/uploadUtils.ts similarity index 97% rename from packages/cache/src/internal/uploadUtils.ts rename to packages/cache/src/internal/shared/uploadUtils.ts index 1b4f7af0d1..84777f17da 100644 --- a/packages/cache/src/internal/uploadUtils.ts +++ b/packages/cache/src/internal/shared/uploadUtils.ts @@ -6,8 +6,8 @@ import { BlockBlobParallelUploadOptions } from '@azure/storage-blob' import {TransferProgressEvent} from '@azure/ms-rest-js' -import {InvalidResponseError} from './shared/errors' -import {UploadOptions} from '../options' +import {InvalidResponseError} from './errors' +import {UploadOptions} from '../../options' /** * Class for tracking the upload state and displaying stats. diff --git a/packages/cache/src/internal/shared/util.ts b/packages/cache/src/internal/shared/util.ts deleted file mode 100644 index 36d2ebfdce..0000000000 --- a/packages/cache/src/internal/shared/util.ts +++ /dev/null @@ -1,76 +0,0 @@ -import {debug, setSecret} from '@actions/core' - -/** - * Masks the `sig` parameter in a URL and sets it as a secret. - * - * @param url - The URL containing the signature parameter to mask - * @remarks - * This function attempts to parse the provided URL and identify the 'sig' query parameter. - * If found, it registers both the raw and URL-encoded signature values as secrets using - * the Actions `setSecret` API, which prevents them from being displayed in logs. - * - * The function handles errors gracefully if URL parsing fails, logging them as debug messages. - * - * @example - * ```typescript - * // Mask a signature in an Azure SAS token URL - * maskSigUrl('https://example.blob.core.windows.net/container/file.txt?sig=abc123&se=2023-01-01'); - * ``` - */ -export function maskSigUrl(url: string): void { - if (!url) return - try { - const parsedUrl = new URL(url) - const signature = parsedUrl.searchParams.get('sig') - if (signature) { - setSecret(signature) - setSecret(encodeURIComponent(signature)) - } - } catch (error) { - debug( - `Failed to parse URL: ${url} ${ - error instanceof Error ? error.message : String(error) - }` - ) - } -} - -/** - * Masks sensitive information in URLs containing signature parameters. - * Currently supports masking 'sig' parameters in the 'signed_upload_url' - * and 'signed_download_url' properties of the provided object. - * - * @param body - The object should contain a signature - * @remarks - * This function extracts URLs from the object properties and calls maskSigUrl - * on each one to redact sensitive signature information. The function doesn't - * modify the original object; it only marks the signatures as secrets for - * logging purposes. - * - * @example - * ```typescript - * const responseBody = { - * signed_upload_url: 'https://blob.core.windows.net/?sig=abc123', - * signed_download_url: 'https://blob.core/windows.net/?sig=def456' - * }; - * maskSecretUrls(responseBody); - * ``` - */ -export function maskSecretUrls(body: Record | null): void { - if (typeof body !== 'object' || body === null) { - debug('body is not an object or is null') - return - } - if ( - 'signed_upload_url' in body && - typeof body.signed_upload_url === 'string' - ) { - maskSigUrl(body.signed_upload_url) - } - if ( - 'signed_download_url' in body && - typeof body.signed_download_url === 'string' - ) { - maskSigUrl(body.signed_download_url) - } -} diff --git a/packages/cache/src/internal/tar.ts b/packages/cache/src/internal/tar.ts index adf610694f..5a5971c449 100644 --- a/packages/cache/src/internal/tar.ts +++ b/packages/cache/src/internal/tar.ts @@ -2,7 +2,7 @@ import {exec} from '@actions/exec' import * as io from '@actions/io' import {existsSync, writeFileSync} from 'fs' import * as path from 'path' -import * as utils from './cacheUtils' +import * as utils from './shared/cacheUtils' import {ArchiveTool} from './contracts' import { CompressionMethod, From e3edd33a4336b3a14a475fe7d403aa0bf42adf51 Mon Sep 17 00:00:00 2001 From: Salman Chishti Date: Mon, 24 Mar 2025 06:36:34 -0700 Subject: [PATCH 02/16] update the import for tests --- packages/cache/__tests__/util.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/cache/__tests__/util.test.ts b/packages/cache/__tests__/util.test.ts index 3ba3bba744..a2e147a2f2 100644 --- a/packages/cache/__tests__/util.test.ts +++ b/packages/cache/__tests__/util.test.ts @@ -1,4 +1,4 @@ -import {maskSigUrl, maskSecretUrls} from '../src/internal/shared/util' +import {maskSigUrl, maskSecretUrls} from '../src/internal/shared/requestUtils' import {setSecret, debug} from '@actions/core' jest.mock('@actions/core') From 91d79116fe6974a60a75ee2cf827516f5dc541f1 Mon Sep 17 00:00:00 2001 From: Salman Chishti Date: Mon, 24 Mar 2025 06:38:15 -0700 Subject: [PATCH 03/16] update imports --- packages/cache/__tests__/restoreCacheV2.test.ts | 2 +- packages/cache/__tests__/saveCache.test.ts | 2 +- packages/cache/__tests__/saveCacheV2.test.ts | 2 +- packages/cache/__tests__/tar.test.ts | 2 +- packages/cache/__tests__/uploadUtils.test.ts | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/cache/__tests__/restoreCacheV2.test.ts b/packages/cache/__tests__/restoreCacheV2.test.ts index 485b8aebce..064c473e5b 100644 --- a/packages/cache/__tests__/restoreCacheV2.test.ts +++ b/packages/cache/__tests__/restoreCacheV2.test.ts @@ -2,7 +2,7 @@ import * as core from '@actions/core' import * as path from 'path' import * as tar from '../src/internal/tar' import * as config from '../src/internal/config' -import * as cacheUtils from '../src/internal/cacheUtils' +import * as cacheUtils from '../src/internal/shared/cacheUtils' import * as cacheHttpClient from '../src/internal/cacheHttpClient' import {restoreCache} from '../src/cache' import {CacheFilename, CompressionMethod} from '../src/internal/constants' diff --git a/packages/cache/__tests__/saveCache.test.ts b/packages/cache/__tests__/saveCache.test.ts index e5ed695b1f..cb69faee9a 100644 --- a/packages/cache/__tests__/saveCache.test.ts +++ b/packages/cache/__tests__/saveCache.test.ts @@ -2,7 +2,7 @@ import * as core from '@actions/core' import * as path from 'path' import {saveCache} from '../src/cache' import * as cacheHttpClient from '../src/internal/cacheHttpClient' -import * as cacheUtils from '../src/internal/cacheUtils' +import * as cacheUtils from '../src/internal/shared/cacheUtils' import * as config from '../src/internal/config' import {CacheFilename, CompressionMethod} from '../src/internal/constants' import * as tar from '../src/internal/tar' diff --git a/packages/cache/__tests__/saveCacheV2.test.ts b/packages/cache/__tests__/saveCacheV2.test.ts index e96c2ac9da..3da80d46b3 100644 --- a/packages/cache/__tests__/saveCacheV2.test.ts +++ b/packages/cache/__tests__/saveCacheV2.test.ts @@ -1,7 +1,7 @@ import * as core from '@actions/core' import * as path from 'path' import {saveCache} from '../src/cache' -import * as cacheUtils from '../src/internal/cacheUtils' +import * as cacheUtils from '../src/internal/shared/cacheUtils' import {CacheFilename, CompressionMethod} from '../src/internal/constants' import * as config from '../src/internal/config' import * as tar from '../src/internal/tar' diff --git a/packages/cache/__tests__/tar.test.ts b/packages/cache/__tests__/tar.test.ts index 4145d9a946..6a72420716 100644 --- a/packages/cache/__tests__/tar.test.ts +++ b/packages/cache/__tests__/tar.test.ts @@ -10,7 +10,7 @@ import { TarFilename } from '../src/internal/constants' import * as tar from '../src/internal/tar' -import * as utils from '../src/internal/cacheUtils' +import * as utils from '../src/internal/shared/cacheUtils' // eslint-disable-next-line @typescript-eslint/no-require-imports import fs = require('fs') diff --git a/packages/cache/__tests__/uploadUtils.test.ts b/packages/cache/__tests__/uploadUtils.test.ts index 2f0b8b554f..1f8b0733fc 100644 --- a/packages/cache/__tests__/uploadUtils.test.ts +++ b/packages/cache/__tests__/uploadUtils.test.ts @@ -1,4 +1,4 @@ -import * as uploadUtils from '../src/internal/uploadUtils' +import * as uploadUtils from '../src/internal/shared/uploadUtils' import {TransferProgressEvent} from '@azure/ms-rest-js' test('upload progress tracked correctly', () => { From 3ba349f95d28099c730a0e8aee1b14e2bf6525d9 Mon Sep 17 00:00:00 2001 From: Salman Chishti Date: Mon, 24 Mar 2025 06:39:24 -0700 Subject: [PATCH 04/16] update github package --- packages/github/src/internal/{ => shared}/utils.ts | 0 packages/github/src/utils.ts | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename packages/github/src/internal/{ => shared}/utils.ts (100%) diff --git a/packages/github/src/internal/utils.ts b/packages/github/src/internal/shared/utils.ts similarity index 100% rename from packages/github/src/internal/utils.ts rename to packages/github/src/internal/shared/utils.ts diff --git a/packages/github/src/utils.ts b/packages/github/src/utils.ts index 0c50d6ac82..45d0273f02 100644 --- a/packages/github/src/utils.ts +++ b/packages/github/src/utils.ts @@ -1,5 +1,5 @@ import * as Context from './context' -import * as Utils from './internal/utils' +import * as Utils from './internal/shared/utils' // octokit + plugins import {Octokit} from '@octokit/core' From 3faea27ea191a0dba44c8c7fe5363567ec0b1ce7 Mon Sep 17 00:00:00 2001 From: Salman Chishti Date: Mon, 24 Mar 2025 06:46:18 -0700 Subject: [PATCH 05/16] Revert "update github package" This reverts commit 3ba349f95d28099c730a0e8aee1b14e2bf6525d9. --- packages/github/src/internal/{shared => }/utils.ts | 0 packages/github/src/utils.ts | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename packages/github/src/internal/{shared => }/utils.ts (100%) diff --git a/packages/github/src/internal/shared/utils.ts b/packages/github/src/internal/utils.ts similarity index 100% rename from packages/github/src/internal/shared/utils.ts rename to packages/github/src/internal/utils.ts diff --git a/packages/github/src/utils.ts b/packages/github/src/utils.ts index 45d0273f02..0c50d6ac82 100644 --- a/packages/github/src/utils.ts +++ b/packages/github/src/utils.ts @@ -1,5 +1,5 @@ import * as Context from './context' -import * as Utils from './internal/shared/utils' +import * as Utils from './internal/utils' // octokit + plugins import {Octokit} from '@octokit/core' From a8f4bcd169102b547d0440a91ef29d32d02b0329 Mon Sep 17 00:00:00 2001 From: Salman Chishti Date: Mon, 24 Mar 2025 07:08:26 -0700 Subject: [PATCH 06/16] update remaining tests --- packages/cache/__tests__/cacheHttpClient.test.ts | 8 ++++---- packages/cache/__tests__/cacheUtils.test.ts | 2 +- packages/cache/__tests__/downloadUtils.test.ts | 2 +- packages/cache/__tests__/requestUtils.test.ts | 4 ++-- packages/cache/__tests__/restoreCache.test.ts | 2 +- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/packages/cache/__tests__/cacheHttpClient.test.ts b/packages/cache/__tests__/cacheHttpClient.test.ts index e2201cd1cb..d68381475a 100644 --- a/packages/cache/__tests__/cacheHttpClient.test.ts +++ b/packages/cache/__tests__/cacheHttpClient.test.ts @@ -1,7 +1,7 @@ -import {downloadCache} from '../src/internal/cacheHttpClient' -import {getCacheVersion} from '../src/internal/cacheUtils' -import {CompressionMethod} from '../src/internal/constants' -import * as downloadUtils from '../src/internal/downloadUtils' +import {downloadCache} from '../src/internal/shared/cacheHttpClient' +import {getCacheVersion} from '../src/internal/shared/cacheUtils' +import {CompressionMethod} from '../src/internal/shared/constants' +import * as downloadUtils from '../src/internal/shared/downloadUtils' import {DownloadOptions, getDownloadOptions} from '../src/options' jest.mock('../src/internal/downloadUtils') diff --git a/packages/cache/__tests__/cacheUtils.test.ts b/packages/cache/__tests__/cacheUtils.test.ts index fad045b47e..2adddd5cc3 100644 --- a/packages/cache/__tests__/cacheUtils.test.ts +++ b/packages/cache/__tests__/cacheUtils.test.ts @@ -1,6 +1,6 @@ import {promises as fs} from 'fs' import * as path from 'path' -import * as cacheUtils from '../src/internal/cacheUtils' +import * as cacheUtils from '../src/internal/shared/cacheUtils' beforeEach(() => { jest.resetModules() diff --git a/packages/cache/__tests__/downloadUtils.test.ts b/packages/cache/__tests__/downloadUtils.test.ts index 4cc089af89..067303f64d 100644 --- a/packages/cache/__tests__/downloadUtils.test.ts +++ b/packages/cache/__tests__/downloadUtils.test.ts @@ -1,5 +1,5 @@ import * as core from '@actions/core' -import {DownloadProgress} from '../src/internal/downloadUtils' +import {DownloadProgress} from '../src/internal/shared/downloadUtils' test('download progress tracked correctly', () => { const progress = new DownloadProgress(1000) diff --git a/packages/cache/__tests__/requestUtils.test.ts b/packages/cache/__tests__/requestUtils.test.ts index 05fc573bdf..46dc0e804a 100644 --- a/packages/cache/__tests__/requestUtils.test.ts +++ b/packages/cache/__tests__/requestUtils.test.ts @@ -1,6 +1,6 @@ -import {retry, retryTypedResponse} from '../src/internal/requestUtils' +import {retry, retryTypedResponse} from '../src/internal/shared/requestUtils' import {HttpClientError} from '@actions/http-client' -import * as requestUtils from '../src/internal/requestUtils' +import * as requestUtils from '../src/internal/shared/requestUtils' interface ITestResponse { statusCode: number diff --git a/packages/cache/__tests__/restoreCache.test.ts b/packages/cache/__tests__/restoreCache.test.ts index 7992490e5a..edd3ae39e8 100644 --- a/packages/cache/__tests__/restoreCache.test.ts +++ b/packages/cache/__tests__/restoreCache.test.ts @@ -2,7 +2,7 @@ import * as core from '@actions/core' import * as path from 'path' import {restoreCache} from '../src/cache' import * as cacheHttpClient from '../src/internal/cacheHttpClient' -import * as cacheUtils from '../src/internal/cacheUtils' +import * as cacheUtils from '../src/internal/shared/cacheUtils' import {CacheFilename, CompressionMethod} from '../src/internal/constants' import {ArtifactCacheEntry} from '../src/internal/contracts' import * as tar from '../src/internal/tar' From 64a54fa9b2f3750fb517796924a21ded8824106e Mon Sep 17 00:00:00 2001 From: Salman Chishti Date: Mon, 24 Mar 2025 07:17:18 -0700 Subject: [PATCH 07/16] update structure --- packages/cache/__tests__/cacheHttpClient.test.ts | 2 +- packages/cache/__tests__/restoreCache.test.ts | 5 ++++- packages/cache/__tests__/restoreCacheV2.test.ts | 5 ++++- packages/cache/__tests__/saveCache.test.ts | 5 ++++- packages/cache/__tests__/saveCacheV2.test.ts | 5 ++++- packages/cache/__tests__/tar.test.ts | 2 +- packages/cache/src/cache.ts | 4 ++-- .../src/internal/{shared => }/cacheTwirpClient.ts | 12 ++++++------ packages/cache/src/internal/contracts.d.ts | 2 +- packages/cache/src/internal/shared/cacheUtils.ts | 2 +- .../cache/src/internal/{ => shared}/constants.ts | 0 packages/cache/src/internal/shared/downloadUtils.ts | 2 +- packages/cache/src/internal/shared/requestUtils.ts | 2 +- packages/cache/src/internal/tar.ts | 2 +- 14 files changed, 31 insertions(+), 19 deletions(-) rename packages/cache/src/internal/{shared => }/cacheTwirpClient.ts (94%) rename packages/cache/src/internal/{ => shared}/constants.ts (100%) diff --git a/packages/cache/__tests__/cacheHttpClient.test.ts b/packages/cache/__tests__/cacheHttpClient.test.ts index d68381475a..607cf07042 100644 --- a/packages/cache/__tests__/cacheHttpClient.test.ts +++ b/packages/cache/__tests__/cacheHttpClient.test.ts @@ -1,4 +1,4 @@ -import {downloadCache} from '../src/internal/shared/cacheHttpClient' +import {downloadCache} from '../src/internal/cacheHttpClient' import {getCacheVersion} from '../src/internal/shared/cacheUtils' import {CompressionMethod} from '../src/internal/shared/constants' import * as downloadUtils from '../src/internal/shared/downloadUtils' diff --git a/packages/cache/__tests__/restoreCache.test.ts b/packages/cache/__tests__/restoreCache.test.ts index edd3ae39e8..f27c6c583d 100644 --- a/packages/cache/__tests__/restoreCache.test.ts +++ b/packages/cache/__tests__/restoreCache.test.ts @@ -3,7 +3,10 @@ import * as path from 'path' import {restoreCache} from '../src/cache' import * as cacheHttpClient from '../src/internal/cacheHttpClient' import * as cacheUtils from '../src/internal/shared/cacheUtils' -import {CacheFilename, CompressionMethod} from '../src/internal/constants' +import { + CacheFilename, + CompressionMethod +} from '../src/internal/shared/constants' import {ArtifactCacheEntry} from '../src/internal/contracts' import * as tar from '../src/internal/tar' diff --git a/packages/cache/__tests__/restoreCacheV2.test.ts b/packages/cache/__tests__/restoreCacheV2.test.ts index 064c473e5b..72a290e5fc 100644 --- a/packages/cache/__tests__/restoreCacheV2.test.ts +++ b/packages/cache/__tests__/restoreCacheV2.test.ts @@ -5,7 +5,10 @@ import * as config from '../src/internal/config' import * as cacheUtils from '../src/internal/shared/cacheUtils' import * as cacheHttpClient from '../src/internal/cacheHttpClient' import {restoreCache} from '../src/cache' -import {CacheFilename, CompressionMethod} from '../src/internal/constants' +import { + CacheFilename, + CompressionMethod +} from '../src/internal/shared/constants' import {CacheServiceClientJSON} from '../src/generated/results/api/v1/cache.twirp-client' import {DownloadOptions} from '../src/options' diff --git a/packages/cache/__tests__/saveCache.test.ts b/packages/cache/__tests__/saveCache.test.ts index cb69faee9a..9e6227f7c4 100644 --- a/packages/cache/__tests__/saveCache.test.ts +++ b/packages/cache/__tests__/saveCache.test.ts @@ -4,7 +4,10 @@ import {saveCache} from '../src/cache' import * as cacheHttpClient from '../src/internal/cacheHttpClient' import * as cacheUtils from '../src/internal/shared/cacheUtils' import * as config from '../src/internal/config' -import {CacheFilename, CompressionMethod} from '../src/internal/constants' +import { + CacheFilename, + CompressionMethod +} from '../src/internal/shared/constants' import * as tar from '../src/internal/tar' import {TypedResponse} from '@actions/http-client/lib/interfaces' import { diff --git a/packages/cache/__tests__/saveCacheV2.test.ts b/packages/cache/__tests__/saveCacheV2.test.ts index 3da80d46b3..df04fe2034 100644 --- a/packages/cache/__tests__/saveCacheV2.test.ts +++ b/packages/cache/__tests__/saveCacheV2.test.ts @@ -2,7 +2,10 @@ import * as core from '@actions/core' import * as path from 'path' import {saveCache} from '../src/cache' import * as cacheUtils from '../src/internal/shared/cacheUtils' -import {CacheFilename, CompressionMethod} from '../src/internal/constants' +import { + CacheFilename, + CompressionMethod +} from '../src/internal/shared/constants' import * as config from '../src/internal/config' import * as tar from '../src/internal/tar' import {CacheServiceClientJSON} from '../src/generated/results/api/v1/cache.twirp-client' diff --git a/packages/cache/__tests__/tar.test.ts b/packages/cache/__tests__/tar.test.ts index 6a72420716..211824c8fd 100644 --- a/packages/cache/__tests__/tar.test.ts +++ b/packages/cache/__tests__/tar.test.ts @@ -8,7 +8,7 @@ import { ManifestFilename, SystemTarPathOnWindows, TarFilename -} from '../src/internal/constants' +} from '../src/internal/shared/constants' import * as tar from '../src/internal/tar' import * as utils from '../src/internal/shared/cacheUtils' // eslint-disable-next-line @typescript-eslint/no-require-imports diff --git a/packages/cache/src/cache.ts b/packages/cache/src/cache.ts index 6f8880a8b5..7f08ce4ac4 100644 --- a/packages/cache/src/cache.ts +++ b/packages/cache/src/cache.ts @@ -2,7 +2,7 @@ import * as core from '@actions/core' import * as path from 'path' import * as utils from './internal/shared/cacheUtils' import * as cacheHttpClient from './internal/cacheHttpClient' -import * as cacheTwirpClient from './internal/shared/cacheTwirpClient' +import * as cacheTwirpClient from './internal/cacheTwirpClient' import {getCacheServiceVersion, isGhes} from './internal/config' import {DownloadOptions, UploadOptions} from './options' import {createTar, extractTar, listTar} from './internal/tar' @@ -12,7 +12,7 @@ import { FinalizeCacheEntryUploadResponse, GetCacheEntryDownloadURLRequest } from './generated/results/api/v1/cache' -import {CacheFileSizeLimit} from './internal/constants' +import {CacheFileSizeLimit} from './internal/shared/constants' export class ValidationError extends Error { constructor(message: string) { super(message) diff --git a/packages/cache/src/internal/shared/cacheTwirpClient.ts b/packages/cache/src/internal/cacheTwirpClient.ts similarity index 94% rename from packages/cache/src/internal/shared/cacheTwirpClient.ts rename to packages/cache/src/internal/cacheTwirpClient.ts index c662b43cb8..4e4c38547d 100644 --- a/packages/cache/src/internal/shared/cacheTwirpClient.ts +++ b/packages/cache/src/internal/cacheTwirpClient.ts @@ -1,12 +1,12 @@ import {info, debug} from '@actions/core' -import {getUserAgentString} from './user-agent' -import {NetworkError, UsageError} from './errors' -import {getCacheServiceURL} from '../config' -import {getRuntimeToken} from './cacheUtils' +import {getUserAgentString} from './shared/user-agent' +import {NetworkError, UsageError} from './shared/errors' +import {getCacheServiceURL} from './config' +import {getRuntimeToken} from './shared/cacheUtils' import {BearerCredentialHandler} from '@actions/http-client/lib/auth' import {HttpClient, HttpClientResponse, HttpCodes} from '@actions/http-client' -import {CacheServiceClientJSON} from '../../generated/results/api/v1/cache.twirp-client' -import {maskSecretUrls} from './requestUtils' +import {CacheServiceClientJSON} from '../generated/results/api/v1/cache.twirp-client' +import {maskSecretUrls} from './shared/requestUtils' // The twirp http client must implement this interface interface Rpc { diff --git a/packages/cache/src/internal/contracts.d.ts b/packages/cache/src/internal/contracts.d.ts index 6fcd9427c8..8aa44e25a2 100644 --- a/packages/cache/src/internal/contracts.d.ts +++ b/packages/cache/src/internal/contracts.d.ts @@ -1,4 +1,4 @@ -import {CompressionMethod} from './constants' +import {CompressionMethod} from './shared/constants' import {TypedResponse} from '@actions/http-client/lib/interfaces' import {HttpClientError} from '@actions/http-client' diff --git a/packages/cache/src/internal/shared/cacheUtils.ts b/packages/cache/src/internal/shared/cacheUtils.ts index 12adc1b587..de9053eae0 100644 --- a/packages/cache/src/internal/shared/cacheUtils.ts +++ b/packages/cache/src/internal/shared/cacheUtils.ts @@ -11,7 +11,7 @@ import { CacheFilename, CompressionMethod, GnuTarPathOnWindows -} from '../constants' +} from './constants' const versionSalt = '1.0' diff --git a/packages/cache/src/internal/constants.ts b/packages/cache/src/internal/shared/constants.ts similarity index 100% rename from packages/cache/src/internal/constants.ts rename to packages/cache/src/internal/shared/constants.ts diff --git a/packages/cache/src/internal/shared/downloadUtils.ts b/packages/cache/src/internal/shared/downloadUtils.ts index 145ab26866..dbe87ebd1d 100644 --- a/packages/cache/src/internal/shared/downloadUtils.ts +++ b/packages/cache/src/internal/shared/downloadUtils.ts @@ -8,7 +8,7 @@ import * as stream from 'stream' import * as util from 'util' import * as utils from './cacheUtils' -import {SocketTimeout} from '../constants' +import {SocketTimeout} from './constants' import {DownloadOptions} from '../../options' import {retryHttpClientResponse} from './requestUtils' diff --git a/packages/cache/src/internal/shared/requestUtils.ts b/packages/cache/src/internal/shared/requestUtils.ts index 2b1bb031a5..67701e5506 100644 --- a/packages/cache/src/internal/shared/requestUtils.ts +++ b/packages/cache/src/internal/shared/requestUtils.ts @@ -4,7 +4,7 @@ import { HttpClientError, HttpClientResponse } from '@actions/http-client' -import {DefaultRetryDelay, DefaultRetryAttempts} from '../constants' +import {DefaultRetryDelay, DefaultRetryAttempts} from './constants' import {ITypedResponseWithError} from '../contracts' import {debug, setSecret} from '@actions/core' diff --git a/packages/cache/src/internal/tar.ts b/packages/cache/src/internal/tar.ts index 5a5971c449..bebf9b5fd6 100644 --- a/packages/cache/src/internal/tar.ts +++ b/packages/cache/src/internal/tar.ts @@ -10,7 +10,7 @@ import { ArchiveToolType, TarFilename, ManifestFilename -} from './constants' +} from './shared/constants' const IS_WINDOWS = process.platform === 'win32' From 6b7e88520a27218ec5f89a99a2741355ca460ee0 Mon Sep 17 00:00:00 2001 From: Salman Chishti Date: Mon, 24 Mar 2025 07:27:07 -0700 Subject: [PATCH 08/16] update mocks --- packages/cache/__tests__/cacheHttpClient.test.ts | 2 +- packages/cache/__tests__/restoreCacheV2.test.ts | 6 +++--- packages/cache/__tests__/saveCache.test.ts | 2 +- packages/cache/__tests__/saveCacheV2.test.ts | 2 +- packages/cache/src/cache.ts | 2 +- packages/cache/src/internal/cacheHttpClient.ts | 2 +- packages/cache/src/internal/cacheTwirpClient.ts | 2 +- packages/cache/src/internal/{ => shared}/config.ts | 0 8 files changed, 9 insertions(+), 9 deletions(-) rename packages/cache/src/internal/{ => shared}/config.ts (100%) diff --git a/packages/cache/__tests__/cacheHttpClient.test.ts b/packages/cache/__tests__/cacheHttpClient.test.ts index 607cf07042..bdb9e68dd0 100644 --- a/packages/cache/__tests__/cacheHttpClient.test.ts +++ b/packages/cache/__tests__/cacheHttpClient.test.ts @@ -4,7 +4,7 @@ import {CompressionMethod} from '../src/internal/shared/constants' import * as downloadUtils from '../src/internal/shared/downloadUtils' import {DownloadOptions, getDownloadOptions} from '../src/options' -jest.mock('../src/internal/downloadUtils') +jest.mock('../src/internal/shared/downloadUtils') test('getCacheVersion does not mutate arguments', async () => { const paths = ['node_modules'] diff --git a/packages/cache/__tests__/restoreCacheV2.test.ts b/packages/cache/__tests__/restoreCacheV2.test.ts index 72a290e5fc..78ec0bd3fa 100644 --- a/packages/cache/__tests__/restoreCacheV2.test.ts +++ b/packages/cache/__tests__/restoreCacheV2.test.ts @@ -1,7 +1,7 @@ import * as core from '@actions/core' import * as path from 'path' import * as tar from '../src/internal/tar' -import * as config from '../src/internal/config' +import * as config from '../src/internal/shared/config' import * as cacheUtils from '../src/internal/shared/cacheUtils' import * as cacheHttpClient from '../src/internal/cacheHttpClient' import {restoreCache} from '../src/cache' @@ -13,8 +13,8 @@ import {CacheServiceClientJSON} from '../src/generated/results/api/v1/cache.twir import {DownloadOptions} from '../src/options' jest.mock('../src/internal/cacheHttpClient') -jest.mock('../src/internal/cacheUtils') -jest.mock('../src/internal/config') +jest.mock('../src/internal/shared/cacheUtils') +jest.mock('../src/internal/shared/config') jest.mock('../src/internal/tar') let logDebugMock: jest.SpyInstance diff --git a/packages/cache/__tests__/saveCache.test.ts b/packages/cache/__tests__/saveCache.test.ts index 9e6227f7c4..50de976f66 100644 --- a/packages/cache/__tests__/saveCache.test.ts +++ b/packages/cache/__tests__/saveCache.test.ts @@ -3,7 +3,7 @@ import * as path from 'path' import {saveCache} from '../src/cache' import * as cacheHttpClient from '../src/internal/cacheHttpClient' import * as cacheUtils from '../src/internal/shared/cacheUtils' -import * as config from '../src/internal/config' +import * as config from '../src/internal/shared/config' import { CacheFilename, CompressionMethod diff --git a/packages/cache/__tests__/saveCacheV2.test.ts b/packages/cache/__tests__/saveCacheV2.test.ts index df04fe2034..977535817f 100644 --- a/packages/cache/__tests__/saveCacheV2.test.ts +++ b/packages/cache/__tests__/saveCacheV2.test.ts @@ -6,7 +6,7 @@ import { CacheFilename, CompressionMethod } from '../src/internal/shared/constants' -import * as config from '../src/internal/config' +import * as config from '../src/internal/shared/config' import * as tar from '../src/internal/tar' import {CacheServiceClientJSON} from '../src/generated/results/api/v1/cache.twirp-client' import * as cacheHttpClient from '../src/internal/cacheHttpClient' diff --git a/packages/cache/src/cache.ts b/packages/cache/src/cache.ts index 7f08ce4ac4..4374c5f0dc 100644 --- a/packages/cache/src/cache.ts +++ b/packages/cache/src/cache.ts @@ -3,7 +3,7 @@ import * as path from 'path' import * as utils from './internal/shared/cacheUtils' import * as cacheHttpClient from './internal/cacheHttpClient' import * as cacheTwirpClient from './internal/cacheTwirpClient' -import {getCacheServiceVersion, isGhes} from './internal/config' +import {getCacheServiceVersion, isGhes} from './internal/shared/config' import {DownloadOptions, UploadOptions} from './options' import {createTar, extractTar, listTar} from './internal/tar' import { diff --git a/packages/cache/src/internal/cacheHttpClient.ts b/packages/cache/src/internal/cacheHttpClient.ts index 2821acaef7..e62636c92f 100644 --- a/packages/cache/src/internal/cacheHttpClient.ts +++ b/packages/cache/src/internal/cacheHttpClient.ts @@ -34,7 +34,7 @@ import { retryHttpClientResponse, retryTypedResponse } from './shared/requestUtils' -import {getCacheServiceURL} from './config' +import {getCacheServiceURL} from './shared/config' import {getUserAgentString} from './shared/user-agent' function getCacheApiUrl(resource: string): string { diff --git a/packages/cache/src/internal/cacheTwirpClient.ts b/packages/cache/src/internal/cacheTwirpClient.ts index 4e4c38547d..7ac9e65706 100644 --- a/packages/cache/src/internal/cacheTwirpClient.ts +++ b/packages/cache/src/internal/cacheTwirpClient.ts @@ -1,7 +1,7 @@ import {info, debug} from '@actions/core' import {getUserAgentString} from './shared/user-agent' import {NetworkError, UsageError} from './shared/errors' -import {getCacheServiceURL} from './config' +import {getCacheServiceURL} from './shared/config' import {getRuntimeToken} from './shared/cacheUtils' import {BearerCredentialHandler} from '@actions/http-client/lib/auth' import {HttpClient, HttpClientResponse, HttpCodes} from '@actions/http-client' diff --git a/packages/cache/src/internal/config.ts b/packages/cache/src/internal/shared/config.ts similarity index 100% rename from packages/cache/src/internal/config.ts rename to packages/cache/src/internal/shared/config.ts From 80a12eba9d71a025092d44c54032cb62da4edf3c Mon Sep 17 00:00:00 2001 From: Salman Chishti Date: Mon, 24 Mar 2025 08:04:34 -0700 Subject: [PATCH 09/16] update import --- packages/cache/__tests__/config.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/cache/__tests__/config.test.ts b/packages/cache/__tests__/config.test.ts index 52d86d3620..0f454eff51 100644 --- a/packages/cache/__tests__/config.test.ts +++ b/packages/cache/__tests__/config.test.ts @@ -1,4 +1,4 @@ -import * as config from '../src/internal/config' +import * as config from '../src/internal/shared/config' beforeEach(() => { jest.resetModules() From 2e4b3004f85252973a387d9b00f322c8cce1eb51 Mon Sep 17 00:00:00 2001 From: Salman Chishti Date: Mon, 24 Mar 2025 10:43:07 -0700 Subject: [PATCH 10/16] update cache util import --- packages/cache/__tests__/restoreCache.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/cache/__tests__/restoreCache.test.ts b/packages/cache/__tests__/restoreCache.test.ts index f27c6c583d..f172c1800d 100644 --- a/packages/cache/__tests__/restoreCache.test.ts +++ b/packages/cache/__tests__/restoreCache.test.ts @@ -11,7 +11,7 @@ import {ArtifactCacheEntry} from '../src/internal/contracts' import * as tar from '../src/internal/tar' jest.mock('../src/internal/cacheHttpClient') -jest.mock('../src/internal/cacheUtils') +jest.mock('../src/internal/shared/cacheUtils') jest.mock('../src/internal/tar') beforeAll(() => { From 2639c8b7c9f3c9d09c91bf2d2743fc059b1c52d6 Mon Sep 17 00:00:00 2001 From: Salman Chishti Date: Mon, 24 Mar 2025 10:43:58 -0700 Subject: [PATCH 11/16] update import --- packages/cache/__tests__/saveCache.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/cache/__tests__/saveCache.test.ts b/packages/cache/__tests__/saveCache.test.ts index 50de976f66..5adbf364ea 100644 --- a/packages/cache/__tests__/saveCache.test.ts +++ b/packages/cache/__tests__/saveCache.test.ts @@ -17,8 +17,8 @@ import { import {HttpClientError} from '@actions/http-client' jest.mock('../src/internal/cacheHttpClient') -jest.mock('../src/internal/cacheUtils') -jest.mock('../src/internal/config') +jest.mock('../src/internal/shared/cacheUtils') +jest.mock('../src/internal/shared/config') jest.mock('../src/internal/tar') beforeAll(() => { From 6e3496db1fa5506f7cd880668f252e64b4135674 Mon Sep 17 00:00:00 2001 From: Salman Chishti Date: Mon, 24 Mar 2025 11:22:10 -0700 Subject: [PATCH 12/16] update --- packages/cache/__tests__/saveCache.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/cache/__tests__/saveCache.test.ts b/packages/cache/__tests__/saveCache.test.ts index 5adbf364ea..29a7b98139 100644 --- a/packages/cache/__tests__/saveCache.test.ts +++ b/packages/cache/__tests__/saveCache.test.ts @@ -28,7 +28,7 @@ beforeAll(() => { jest.spyOn(core, 'warning').mockImplementation(() => {}) jest.spyOn(core, 'error').mockImplementation(() => {}) jest.spyOn(cacheUtils, 'getCacheFileName').mockImplementation(cm => { - const actualUtils = jest.requireActual('../src/internal/cacheUtils') + const actualUtils = jest.requireActual('../src/internal/shared/cacheUtils') return actualUtils.getCacheFileName(cm) }) jest.spyOn(cacheUtils, 'resolvePaths').mockImplementation(async filePaths => { From cb0f76a396f55b7ff9c672cb58841a06c35de480 Mon Sep 17 00:00:00 2001 From: Salman Chishti Date: Mon, 24 Mar 2025 11:28:32 -0700 Subject: [PATCH 13/16] update more tests --- packages/cache/__tests__/restoreCache.test.ts | 2 +- packages/cache/__tests__/restoreCacheV2.test.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/cache/__tests__/restoreCache.test.ts b/packages/cache/__tests__/restoreCache.test.ts index f172c1800d..f4c1e16362 100644 --- a/packages/cache/__tests__/restoreCache.test.ts +++ b/packages/cache/__tests__/restoreCache.test.ts @@ -22,7 +22,7 @@ beforeAll(() => { jest.spyOn(core, 'error').mockImplementation(() => {}) jest.spyOn(cacheUtils, 'getCacheFileName').mockImplementation(cm => { - const actualUtils = jest.requireActual('../src/internal/cacheUtils') + const actualUtils = jest.requireActual('../src/internal/shared/cacheUtils') return actualUtils.getCacheFileName(cm) }) }) diff --git a/packages/cache/__tests__/restoreCacheV2.test.ts b/packages/cache/__tests__/restoreCacheV2.test.ts index 78ec0bd3fa..a6006d4e8e 100644 --- a/packages/cache/__tests__/restoreCacheV2.test.ts +++ b/packages/cache/__tests__/restoreCacheV2.test.ts @@ -28,7 +28,7 @@ beforeAll(() => { jest.spyOn(core, 'error').mockImplementation(() => {}) jest.spyOn(cacheUtils, 'getCacheFileName').mockImplementation(cm => { - const actualUtils = jest.requireActual('../src/internal/cacheUtils') + const actualUtils = jest.requireActual('../src/internal/shared/cacheUtils') return actualUtils.getCacheFileName(cm) }) From b9e5d16f4864c8510ffdfad9e6d7019c4b64102c Mon Sep 17 00:00:00 2001 From: Salman Chishti Date: Mon, 24 Mar 2025 11:46:25 -0700 Subject: [PATCH 14/16] add utils --- packages/cache/src/internal/shared/utils.ts | 168 ++++++++++++++++++++ 1 file changed, 168 insertions(+) create mode 100644 packages/cache/src/internal/shared/utils.ts diff --git a/packages/cache/src/internal/shared/utils.ts b/packages/cache/src/internal/shared/utils.ts new file mode 100644 index 0000000000..b244bf4256 --- /dev/null +++ b/packages/cache/src/internal/shared/utils.ts @@ -0,0 +1,168 @@ +import {debug, setSecret} from '@actions/core' +import * as core from '@actions/core' +import * as exec from '@actions/exec' +import * as glob from '@actions/glob' +import * as io from '@actions/io' +import * as crypto from 'crypto' +import * as fs from 'fs' +import * as path from 'path' +import * as semver from 'semver' +import * as util from 'util' +import { + CacheFilename, + CompressionMethod, + GnuTarPathOnWindows +} from './constants' + +const versionSalt = '1.0' + +// From https://github.com/actions/toolkit/blob/main/packages/tool-cache/src/tool-cache.ts#L23 +export async function createTempDirectory(): Promise { + const IS_WINDOWS = process.platform === 'win32' + + let tempDirectory: string = process.env['RUNNER_TEMP'] || '' + + if (!tempDirectory) { + let baseLocation: string + if (IS_WINDOWS) { + // On Windows use the USERPROFILE env variable + baseLocation = process.env['USERPROFILE'] || 'C:\\' + } else { + if (process.platform === 'darwin') { + baseLocation = '/Users' + } else { + baseLocation = '/home' + } + } + tempDirectory = path.join(baseLocation, 'actions', 'temp') + } + + const dest = path.join(tempDirectory, crypto.randomUUID()) + await io.mkdirP(dest) + return dest +} + +export function getArchiveFileSizeInBytes(filePath: string): number { + return fs.statSync(filePath).size +} + +export async function resolvePaths(patterns: string[]): Promise { + const paths: string[] = [] + const workspace = process.env['GITHUB_WORKSPACE'] ?? process.cwd() + const globber = await glob.create(patterns.join('\n'), { + implicitDescendants: false + }) + + for await (const file of globber.globGenerator()) { + const relativeFile = path + .relative(workspace, file) + .replace(new RegExp(`\\${path.sep}`, 'g'), '/') + core.debug(`Matched: ${relativeFile}`) + // Paths are made relative so the tar entries are all relative to the root of the workspace. + if (relativeFile === '') { + // path.relative returns empty string if workspace and file are equal + paths.push('.') + } else { + paths.push(`${relativeFile}`) + } + } + + return paths +} + +export async function unlinkFile(filePath: fs.PathLike): Promise { + return util.promisify(fs.unlink)(filePath) +} + +async function getVersion( + app: string, + additionalArgs: string[] = [] +): Promise { + let versionOutput = '' + additionalArgs.push('--version') + core.debug(`Checking ${app} ${additionalArgs.join(' ')}`) + try { + await exec.exec(`${app}`, additionalArgs, { + ignoreReturnCode: true, + silent: true, + listeners: { + stdout: (data: Buffer): string => (versionOutput += data.toString()), + stderr: (data: Buffer): string => (versionOutput += data.toString()) + } + }) + } catch (err) { + core.debug(err.message) + } + + versionOutput = versionOutput.trim() + core.debug(versionOutput) + return versionOutput +} + +// Use zstandard if possible to maximize cache performance +export async function getCompressionMethod(): Promise { + const versionOutput = await getVersion('zstd', ['--quiet']) + const version = semver.clean(versionOutput) + core.debug(`zstd version: ${version}`) + + if (versionOutput === '') { + return CompressionMethod.Gzip + } else { + return CompressionMethod.ZstdWithoutLong + } +} + +export function getCacheFileName(compressionMethod: CompressionMethod): string { + return compressionMethod === CompressionMethod.Gzip + ? CacheFilename.Gzip + : CacheFilename.Zstd +} + +export async function getGnuTarPathOnWindows(): Promise { + if (fs.existsSync(GnuTarPathOnWindows)) { + return GnuTarPathOnWindows + } + const versionOutput = await getVersion('tar') + return versionOutput.toLowerCase().includes('gnu tar') ? io.which('tar') : '' +} + +export function assertDefined(name: string, value?: T): T { + if (value === undefined) { + throw Error(`Expected ${name} but value was undefiend`) + } + + return value +} + +export function getCacheVersion( + paths: string[], + compressionMethod?: CompressionMethod, + enableCrossOsArchive = false +): string { + // don't pass changes upstream + const components = paths.slice() + + // Add compression method to cache version to restore + // compressed cache as per compression method + if (compressionMethod) { + components.push(compressionMethod) + } + + // Only check for windows platforms if enableCrossOsArchive is false + if (process.platform === 'win32' && !enableCrossOsArchive) { + components.push('windows-only') + } + + // Add salt to cache version to support breaking changes in cache entry + components.push(versionSalt) + + return crypto.createHash('sha256').update(components.join('|')).digest('hex') +} + +export function getRuntimeToken(): string { + const token = process.env['ACTIONS_RUNTIME_TOKEN'] + if (!token) { + throw new Error('Unable to get the ACTIONS_RUNTIME_TOKEN env variable') + } + return token +} From a6375bce31c3439b2751739716a0c596649a533e Mon Sep 17 00:00:00 2001 From: Salman Chishti Date: Mon, 24 Mar 2025 11:49:33 -0700 Subject: [PATCH 15/16] formatting --- packages/cache/src/internal/shared/requestUtils.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/cache/src/internal/shared/requestUtils.ts b/packages/cache/src/internal/shared/requestUtils.ts index 67701e5506..78113c79c4 100644 --- a/packages/cache/src/internal/shared/requestUtils.ts +++ b/packages/cache/src/internal/shared/requestUtils.ts @@ -6,7 +6,6 @@ import { } from '@actions/http-client' import {DefaultRetryDelay, DefaultRetryAttempts} from './constants' import {ITypedResponseWithError} from '../contracts' - import {debug, setSecret} from '@actions/core' export function isSuccessStatusCode(statusCode?: number): boolean { From 8b2d2fc00dd666348414d05885f5b23e5cfff7f4 Mon Sep 17 00:00:00 2001 From: Salman Chishti Date: Mon, 24 Mar 2025 11:54:27 -0700 Subject: [PATCH 16/16] update import --- .../src/internal/shared/cacheTwirpClient.ts | 206 ++++++++++++++++++ 1 file changed, 206 insertions(+) create mode 100644 packages/cache/src/internal/shared/cacheTwirpClient.ts diff --git a/packages/cache/src/internal/shared/cacheTwirpClient.ts b/packages/cache/src/internal/shared/cacheTwirpClient.ts new file mode 100644 index 0000000000..0c35a67a07 --- /dev/null +++ b/packages/cache/src/internal/shared/cacheTwirpClient.ts @@ -0,0 +1,206 @@ +import {info, debug} from '@actions/core' +import {getUserAgentString} from './user-agent' +import {NetworkError, UsageError} from './errors' +import {getCacheServiceURL} from '../shared/config' +import {getRuntimeToken} from '../shared/utils' +import {BearerCredentialHandler} from '@actions/http-client/lib/auth' +import {HttpClient, HttpClientResponse, HttpCodes} from '@actions/http-client' +import {CacheServiceClientJSON} from '../../generated/results/api/v1/cache.twirp-client' +import {maskSecretUrls} from './requestUtils' + +// The twirp http client must implement this interface +interface Rpc { + request( + service: string, + method: string, + contentType: 'application/json' | 'application/protobuf', + data: object | Uint8Array + ): Promise +} + +/** + * This class is a wrapper around the CacheServiceClientJSON class generated by Twirp. + * + * It adds retry logic to the request method, which is not present in the generated client. + * + * This class is used to interact with cache service v2. + */ +class CacheServiceClient implements Rpc { + private httpClient: HttpClient + private baseUrl: string + private maxAttempts = 5 + private baseRetryIntervalMilliseconds = 3000 + private retryMultiplier = 1.5 + + constructor( + userAgent: string, + maxAttempts?: number, + baseRetryIntervalMilliseconds?: number, + retryMultiplier?: number + ) { + const token = getRuntimeToken() + this.baseUrl = getCacheServiceURL() + if (maxAttempts) { + this.maxAttempts = maxAttempts + } + if (baseRetryIntervalMilliseconds) { + this.baseRetryIntervalMilliseconds = baseRetryIntervalMilliseconds + } + if (retryMultiplier) { + this.retryMultiplier = retryMultiplier + } + + this.httpClient = new HttpClient(userAgent, [ + new BearerCredentialHandler(token) + ]) + } + + // This function satisfies the Rpc interface. It is compatible with the JSON + // JSON generated client. + async request( + service: string, + method: string, + contentType: 'application/json' | 'application/protobuf', + data: object | Uint8Array + ): Promise { + const url = new URL(`/twirp/${service}/${method}`, this.baseUrl).href + debug(`[Request] ${method} ${url}`) + const headers = { + 'Content-Type': contentType + } + try { + const {body} = await this.retryableRequest(async () => + this.httpClient.post(url, JSON.stringify(data), headers) + ) + + return body + } catch (error) { + throw new Error(`Failed to ${method}: ${error.message}`) + } + } + + async retryableRequest( + operation: () => Promise + ): Promise<{response: HttpClientResponse; body: object}> { + let attempt = 0 + let errorMessage = '' + let rawBody = '' + while (attempt < this.maxAttempts) { + let isRetryable = false + + try { + const response = await operation() + const statusCode = response.message.statusCode + rawBody = await response.readBody() + debug(`[Response] - ${response.message.statusCode}`) + debug(`Headers: ${JSON.stringify(response.message.headers, null, 2)}`) + const body = JSON.parse(rawBody) + maskSecretUrls(body) + debug(`Body: ${JSON.stringify(body, null, 2)}`) + if (this.isSuccessStatusCode(statusCode)) { + return {response, body} + } + isRetryable = this.isRetryableHttpStatusCode(statusCode) + errorMessage = `Failed request: (${statusCode}) ${response.message.statusMessage}` + if (body.msg) { + if (UsageError.isUsageErrorMessage(body.msg)) { + throw new UsageError() + } + + errorMessage = `${errorMessage}: ${body.msg}` + } + } catch (error) { + if (error instanceof SyntaxError) { + debug(`Raw Body: ${rawBody}`) + } + + if (error instanceof UsageError) { + throw error + } + + if (NetworkError.isNetworkErrorCode(error?.code)) { + throw new NetworkError(error?.code) + } + + isRetryable = true + errorMessage = error.message + } + + if (!isRetryable) { + throw new Error(`Received non-retryable error: ${errorMessage}`) + } + + if (attempt + 1 === this.maxAttempts) { + throw new Error( + `Failed to make request after ${this.maxAttempts} attempts: ${errorMessage}` + ) + } + + const retryTimeMilliseconds = + this.getExponentialRetryTimeMilliseconds(attempt) + info( + `Attempt ${attempt + 1} of ${ + this.maxAttempts + } failed with error: ${errorMessage}. Retrying request in ${retryTimeMilliseconds} ms...` + ) + await this.sleep(retryTimeMilliseconds) + attempt++ + } + + throw new Error(`Request failed`) + } + + isSuccessStatusCode(statusCode?: number): boolean { + if (!statusCode) return false + return statusCode >= 200 && statusCode < 300 + } + + isRetryableHttpStatusCode(statusCode?: number): boolean { + if (!statusCode) return false + + const retryableStatusCodes = [ + HttpCodes.BadGateway, + HttpCodes.GatewayTimeout, + HttpCodes.InternalServerError, + HttpCodes.ServiceUnavailable, + HttpCodes.TooManyRequests + ] + + return retryableStatusCodes.includes(statusCode) + } + + async sleep(milliseconds: number): Promise { + return new Promise(resolve => setTimeout(resolve, milliseconds)) + } + + getExponentialRetryTimeMilliseconds(attempt: number): number { + if (attempt < 0) { + throw new Error('attempt should be a positive integer') + } + + if (attempt === 0) { + return this.baseRetryIntervalMilliseconds + } + + const minTime = + this.baseRetryIntervalMilliseconds * this.retryMultiplier ** attempt + const maxTime = minTime * this.retryMultiplier + + // returns a random number between minTime and maxTime (exclusive) + return Math.trunc(Math.random() * (maxTime - minTime) + minTime) + } +} + +export function internalCacheTwirpClient(options?: { + maxAttempts?: number + retryIntervalMs?: number + retryMultiplier?: number +}): CacheServiceClientJSON { + const client = new CacheServiceClient( + getUserAgentString(), + options?.maxAttempts, + options?.retryIntervalMs, + options?.retryMultiplier + ) + return new CacheServiceClientJSON(client) +}