From f728bb6678f5d8cfc5a642125ad6c53e7ec7ae42 Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Tue, 13 Oct 2026 18:11:29 +0100 Subject: [PATCH 1/4] Add `getDotcomApiDetails` --- src/api-client.ts | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/api-client.ts b/src/api-client.ts index 7aa4d61d75..6dd53e47ef 100644 --- a/src/api-client.ts +++ b/src/api-client.ts @@ -18,9 +18,11 @@ import { RegistryProxyVars, getEnv, exportEnvVar, + ActionsEnvVars, } from "./environment"; import { getCurrentInstance, + getDotcomInstance, GITHUB_DOTCOM_URL, parseGitHubUrl, type GitHubApiCombinedDetails, @@ -187,6 +189,28 @@ export function getApiClient( return createApiClientWithDetails(apiDetails ?? getApiDetails(env)); } +/* + * Gets a `GitHubApiDetails` object describing the GitHub Dotcom instance. + * + * @param env The environment variables to read. + */ +export function getDotcomApiDetails(env: ReadOnlyEnv): GitHubApiDetails { + // If we are currently running on dotcom, then just call `getApiDetails` which will + // construct the appropriate `GitHubApiDetails` from the environment and include the + // value of the `token` input. The `token` input relates to the GitHub instance that + // we are running on only. Therefore, if we are not currently running on dotcom, + // then we construct `GitHubApiDetails` without the `token` value and using the + // hard-coded instance and API URLs. + if (env.getRequired(ActionsEnvVars.GITHUB_SERVER_URL) === GITHUB_DOTCOM_URL) { + return getApiDetails(env); + } else { + return { + auth: undefined, + ...getDotcomInstance(), + }; + } +} + export function getApiClientWithExternalAuth( apiDetails: GitHubApiCombinedDetails, proxy?: ProxyAgent, From f2547e0e8374552ee8c23aedba4752d85792cc91 Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Tue, 13 Oct 2026 18:39:27 +0100 Subject: [PATCH 2/4] Use instance in `DownloadSource` and pass it on to `getApiClient` --- lib/entry-points.js | 45 +++++++++++++++++++++++----------- src/codeql.test.ts | 17 +++---------- src/setup-codeql.test.ts | 13 ++++++++++ src/setup/repository.ts | 51 +++++++++++++++++++------------------- src/start-proxy.test.ts | 53 +++++++++++++++++++++++++--------------- src/testing-utils.ts | 18 ++++++++++++++ 6 files changed, 125 insertions(+), 72 deletions(-) diff --git a/lib/entry-points.js b/lib/entry-points.js index a0e269a8e1..9707f1c661 100644 --- a/lib/entry-points.js +++ b/lib/entry-points.js @@ -170632,6 +170632,7 @@ var import_undici = __toESM(require_undici()); // src/github.ts var GITHUB_DOTCOM_URL = "https://github.com"; +var GITHUB_DOTCOM_API_URL = "https://api.github.com"; function parseGitHubUrl(inputUrl) { const originalUrl = inputUrl; if (inputUrl.indexOf("://") === -1) { @@ -170666,6 +170667,12 @@ function getCurrentInstance(env) { apiURL: env.getRequired("GITHUB_API_URL" /* GITHUB_API_URL */) }; } +function getDotcomInstance() { + return { + url: GITHUB_DOTCOM_URL, + apiURL: GITHUB_DOTCOM_API_URL + }; +} // src/repository.ts function isRepositoryNwoStr(val) { @@ -170761,6 +170768,16 @@ function getApiDetails(env = getEnv()) { function getApiClient(env = getEnv(), apiDetails) { return createApiClientWithDetails(apiDetails ?? getApiDetails(env)); } +function getDotcomApiDetails(env) { + if (env.getRequired("GITHUB_SERVER_URL" /* GITHUB_SERVER_URL */) === GITHUB_DOTCOM_URL) { + return getApiDetails(env); + } else { + return { + auth: void 0, + ...getDotcomInstance() + }; + } +} function getApiClientWithExternalAuth(apiDetails, proxy) { return createApiClientWithDetails(apiDetails, { allowExternal: true, proxy }); } @@ -175781,38 +175798,38 @@ function getCodeQLActionRepository(action) { `The value of '${"GITHUB_ACTION_REPOSITORY" /* GITHUB_ACTION_REPOSITORY */}' is '${actionRepository}', which does not match the expected 'owner/repo' format.` ); } -var DEFAULT_DOWNLOAD_SOURCE = [ - GITHUB_DOTCOM_URL, - CODEQL_DEFAULT_ACTION_REPOSITORY -]; -var isSameDownloadSource = ([srcUrl, srcRepo], [otherUrl, otherRepo]) => { - return srcUrl === otherUrl && srcRepo === otherRepo; +var isSameDownloadSource = ([srcInstance, srcRepo], [otherInstance, otherRepo]) => { + return srcInstance.url === otherInstance.url && srcRepo === otherRepo; }; function getDefaultDotcomDownloadURL(tagName, assetName) { return `${GITHUB_DOTCOM_URL}/${CODEQL_DEFAULT_ACTION_REPOSITORY}/releases/download/${tagName}/${assetName}`; } async function getCodeQLAssetDownloadURL(action, apiDetails, tagName, assetName, assetKind = "CodeQL bundle") { const codeQLActionRepository = getCodeQLActionRepository(action); + const defaultDownloadSource = [ + getDotcomApiDetails(action.env), + CODEQL_DEFAULT_ACTION_REPOSITORY + ]; const potentialDownloadSources = [ // This GitHub instance, and this Action. - [apiDetails.url, codeQLActionRepository], + [apiDetails, codeQLActionRepository], // This GitHub instance, and the canonical Action. - [apiDetails.url, CODEQL_DEFAULT_ACTION_REPOSITORY], + [apiDetails, CODEQL_DEFAULT_ACTION_REPOSITORY], // GitHub.com, and the canonical Action. - DEFAULT_DOWNLOAD_SOURCE + defaultDownloadSource ]; const uniqueDownloadSources = potentialDownloadSources.filter( (source, index2, self2) => { return !self2.slice(0, index2).some((other) => isSameDownloadSource(source, other)); } ); - for (const [instanceURL, repository] of uniqueDownloadSources) { - if (isSameDownloadSource(DEFAULT_DOWNLOAD_SOURCE, [instanceURL, repository])) { + for (const [instance8, repository] of uniqueDownloadSources) { + if (isSameDownloadSource(defaultDownloadSource, [instance8, repository])) { break; } const { owner: repositoryOwner, repo: repositoryName } = parseRepositoryNwo(repository); try { - const release2 = await getApiClient().rest.repos.getReleaseByTag({ + const release2 = await getApiClient(action.env, instance8).rest.repos.getReleaseByTag({ owner: repositoryOwner, repo: repositoryName, tag: tagName @@ -175820,14 +175837,14 @@ async function getCodeQLAssetDownloadURL(action, apiDetails, tagName, assetName, for (const asset of release2.data.assets) { if (asset.name === assetName) { action.logger.info( - `Found ${assetKind} ${assetName} in ${repository} on ${instanceURL} with URL ${asset.url}.` + `Found ${assetKind} ${assetName} in ${repository} on ${instance8.apiURL} with URL ${asset.url}.` ); return asset.url; } } } catch (e) { action.logger.info( - `Looked for ${assetKind} ${assetName} in ${repository} on ${instanceURL} but got error ${e}.` + `Looked for ${assetKind} ${assetName} in ${repository} on ${instance8.apiURL} but got error ${e}.` ); } } diff --git a/src/codeql.test.ts b/src/codeql.test.ts index e70dcd99f9..ff0a4d5b6f 100644 --- a/src/codeql.test.ts +++ b/src/codeql.test.ts @@ -34,6 +34,7 @@ import { makeVersionInfo, createTestConfig, makeMacro, + mockApiDetails, } from "./testing-utils"; import { ToolsDownloadStatusReport } from "./tools-download"; import * as util from "./util"; @@ -126,19 +127,6 @@ function mockReleaseApi({ }); } -function mockApiDetails(apiDetails: Required) { - // This is a workaround to mock `api.getApiDetails()` since it doesn't seem to be possible to - // mock this directly. The difficulty is that `getApiDetails()` is called locally in - // `api-client.ts`, but `sinon.stub(api, "getApiDetails")` only affects calls to - // `getApiDetails()` via an imported `api` module. - sinon - .stub(actionsUtil, "getRequiredInput") - .withArgs("token") - .returns(apiDetails.auth); - process.env["GITHUB_SERVER_URL"] = apiDetails.url; - process.env["GITHUB_API_URL"] = apiDetails.apiURL || ""; -} - async function stubCodeql(): Promise { const codeqlObject = await codeql.getCodeQLForTesting(); sinon.stub(codeqlObject, "getVersion").resolves(makeVersionInfo("2.17.6")); @@ -290,6 +278,7 @@ for (const { cliVersion, tagName, expectedToolcacheVersion } of [ async (t) => { await util.withTmpDir(async (tmpDir) => { setupActionsVars(tmpDir, tmpDir); + mockApiDetails(SAMPLE_DOTCOM_API_DETAILS); const result = await installIntoToolcache({ cliVersion, @@ -470,6 +459,7 @@ test.serial( await util.withTmpDir(async (tmpDir) => { setupActionsVars(tmpDir, tmpDir); + mockApiDetails(SAMPLE_DOTCOM_API_DETAILS); await installIntoToolcache({ tagName: "codeql-bundle-20200601", @@ -517,6 +507,7 @@ test.serial( await util.withTmpDir(async (tmpDir) => { setupActionsVars(tmpDir, tmpDir); + mockApiDetails(SAMPLE_DOTCOM_API_DETAILS); await installIntoToolcache({ tagName: "codeql-bundle-20200601", diff --git a/src/setup-codeql.test.ts b/src/setup-codeql.test.ts index 6bdb29f462..9ad2c6773f 100644 --- a/src/setup-codeql.test.ts +++ b/src/setup-codeql.test.ts @@ -32,6 +32,7 @@ import { getTestEnv, initAllState, makeMacro, + mockApiDetails, mockBundleDownloadApi, setupActionsVars, setupTests, @@ -221,6 +222,7 @@ for (const { await withTmpDir(async (tmpDir) => { setupActionsVars(tmpDir, tmpDir); + mockApiDetails(SAMPLE_DOTCOM_API_DETAILS); const source = await setupCodeql.getCodeQLSource( "linked", SAMPLE_DEFAULT_CLI_VERSION, @@ -255,6 +257,7 @@ test.serial( await withTmpDir(async (tmpDir) => { setupActionsVars(tmpDir, tmpDir); + mockApiDetails(SAMPLE_DOTCOM_API_DETAILS); const source = await setupCodeql.getCodeQLSource( "latest", SAMPLE_DEFAULT_CLI_VERSION, @@ -298,6 +301,7 @@ test.serial( await withTmpDir(async (tmpDir) => { setupActionsVars(tmpDir, tmpDir); + mockApiDetails(SAMPLE_DOTCOM_API_DETAILS); const result = await setupCodeql.setupCodeQLBundle( "linked", SAMPLE_DOTCOM_API_DETAILS, @@ -479,6 +483,7 @@ test.serial( await withTmpDir(async (tmpDir) => { setupActionsVars(tmpDir, tmpDir, { GITHUB_EVENT_NAME: "dynamic" }); + mockApiDetails(SAMPLE_DOTCOM_API_DETAILS); const source = await setupCodeql.getCodeQLSource( undefined, @@ -624,6 +629,7 @@ for (const toolsInput of ["nightly", "nightly-latest"]) { await withTmpDir(async (tmpDir) => { setupActionsVars(tmpDir, tmpDir); + mockApiDetails(SAMPLE_DOTCOM_API_DETAILS); const source = await setupCodeql.getCodeQLSource( toolsInput, SAMPLE_DEFAULT_CLI_VERSION, @@ -945,6 +951,7 @@ const toolcacheInputFallbackMacro = makeMacro({ await withTmpDir(async (tmpDir) => { setupActionsVars(tmpDir, tmpDir); + mockApiDetails(SAMPLE_DOTCOM_API_DETAILS); for (const [k, v] of Object.entries(environment)) { process.env[k] = v; @@ -1165,6 +1172,7 @@ test.serial( await withTmpDir(async (tmpDir) => { setupActionsVars(tmpDir, tmpDir); + mockApiDetails(SAMPLE_DOTCOM_API_DETAILS); const source = await setupCodeql.getCodeQLSource( undefined, PER_LANGUAGE_CLI_VERSION, @@ -1207,6 +1215,7 @@ test.serial( await withTmpDir(async (tmpDir) => { setupActionsVars(tmpDir, tmpDir); + mockApiDetails(SAMPLE_DOTCOM_API_DETAILS); for (const { otherLanguagePacksReason, features } of [ // The per-language feature is disabled. { otherLanguagePacksReason: undefined, features: createFeatures([]) }, @@ -1286,6 +1295,7 @@ for (const fallback of [false, true]) { setupActionsVars(tmpDir, tmpDir, { GITHUB_ACTION_REPOSITORY: "codeql-testing/action-fork", }); + mockApiDetails(SAMPLE_DOTCOM_API_DETAILS); const result = await setupCodeql.setupCodeQLBundle( undefined, SAMPLE_DOTCOM_API_DETAILS, @@ -1553,6 +1563,7 @@ for (const error of [ await withTmpDir(async (tmpDir) => { setupActionsVars(tmpDir, tmpDir); + mockApiDetails(SAMPLE_DOTCOM_API_DETAILS); await t.throwsAsync( setupCodeql.setupCodeQLBundle( undefined, @@ -2259,6 +2270,7 @@ test.serial( async (t) => { await withTmpDir(async (tmpDir) => { setupActionsVars(tmpDir, tmpDir); + mockApiDetails(SAMPLE_DOTCOM_API_DETAILS); delete process.env[EnvVar.HAS_SET_UP_CODEQL]; stubDownloadAndExtract(); @@ -2294,6 +2306,7 @@ test.serial( await withTmpDir(async (tmpDir) => { setupActionsVars(tmpDir, tmpDir); + mockApiDetails(SAMPLE_DOTCOM_API_DETAILS); process.env[ActionsEnvVars.RUNNER_ENVIRONMENT] = "github-hosted"; delete process.env[EnvVar.HAS_SET_UP_CODEQL]; const staleDirectory = createToolcacheEntry( diff --git a/src/setup/repository.ts b/src/setup/repository.ts index 341a278755..fa8b566f26 100644 --- a/src/setup/repository.ts +++ b/src/setup/repository.ts @@ -8,7 +8,6 @@ import { parseRepositoryNwo, type RepositoryNwoStr, } from "../repository"; -import { type InstanceURL } from "../util"; /** The NWO of the standard CodeQL Action repository. */ export const CODEQL_DEFAULT_ACTION_REPOSITORY = "github/codeql-action"; @@ -48,20 +47,14 @@ export function getCodeQLActionRepository( } /**A download source is a pair of strings. */ -export type DownloadSource = [InstanceURL, RepositoryNwoStr]; - -/** GitHub.com, and the canonical Action. */ -export const DEFAULT_DOWNLOAD_SOURCE: DownloadSource = [ - GITHUB_DOTCOM_URL, - CODEQL_DEFAULT_ACTION_REPOSITORY, -]; +export type DownloadSource = [api.GitHubApiDetails, RepositoryNwoStr]; /** Decides if the two provided download sources are the same. */ const isSameDownloadSource = ( - [srcUrl, srcRepo]: DownloadSource, - [otherUrl, otherRepo]: DownloadSource, + [srcInstance, srcRepo]: DownloadSource, + [otherInstance, otherRepo]: DownloadSource, ) => { - return srcUrl === otherUrl && srcRepo === otherRepo; + return srcInstance.url === otherInstance.url && srcRepo === otherRepo; }; /** A download URL is represented as a string. */ @@ -104,13 +97,21 @@ export async function getCodeQLAssetDownloadURL( ): Promise { const codeQLActionRepository = getCodeQLActionRepository(action); + // We construct the default GitHub.com source here, because `getDotcomApiDetails` + // may retrieve the `token` input if we are currently running on GitHub.com and + // we want to avoid doing that statically. + const defaultDownloadSource: DownloadSource = [ + api.getDotcomApiDetails(action.env), + CODEQL_DEFAULT_ACTION_REPOSITORY, + ]; + const potentialDownloadSources: DownloadSource[] = [ // This GitHub instance, and this Action. - [apiDetails.url, codeQLActionRepository], + [apiDetails, codeQLActionRepository], // This GitHub instance, and the canonical Action. - [apiDetails.url, CODEQL_DEFAULT_ACTION_REPOSITORY], + [apiDetails, CODEQL_DEFAULT_ACTION_REPOSITORY], // GitHub.com, and the canonical Action. - DEFAULT_DOWNLOAD_SOURCE, + defaultDownloadSource, ]; // We now filter out any duplicates. @@ -123,34 +124,34 @@ export async function getCodeQLAssetDownloadURL( }, ); - for (const [instanceURL, repository] of uniqueDownloadSources) { + for (const [instance, repository] of uniqueDownloadSources) { // If we've reached the final case, short-circuit the API check since we know the asset exists and is public. - if ( - isSameDownloadSource(DEFAULT_DOWNLOAD_SOURCE, [instanceURL, repository]) - ) { + if (isSameDownloadSource(defaultDownloadSource, [instance, repository])) { break; } const { owner: repositoryOwner, repo: repositoryName } = parseRepositoryNwo(repository); try { - const release = await api.getApiClient().rest.repos.getReleaseByTag({ - owner: repositoryOwner, - repo: repositoryName, - tag: tagName, - }); + const release = await api + .getApiClient(action.env, instance) + .rest.repos.getReleaseByTag({ + owner: repositoryOwner, + repo: repositoryName, + tag: tagName, + }); for (const asset of release.data.assets) { if (asset.name === assetName) { action.logger.info( - `Found ${assetKind} ${assetName} in ${repository} on ${instanceURL} with URL ${asset.url}.`, + `Found ${assetKind} ${assetName} in ${repository} on ${instance.apiURL} with URL ${asset.url}.`, ); return asset.url; } } } catch (e) { action.logger.info( - `Looked for ${assetKind} ${assetName} in ${repository} on ${instanceURL} but got error ${e}.`, + `Looked for ${assetKind} ${assetName} in ${repository} on ${instance.apiURL} but got error ${e}.`, ); } } diff --git a/src/start-proxy.test.ts b/src/start-proxy.test.ts index d01543e709..f432b9b16d 100644 --- a/src/start-proxy.test.ts +++ b/src/start-proxy.test.ts @@ -9,7 +9,7 @@ import { ActionState } from "./action-common"; import * as actionsUtil from "./actions-util"; import * as apiClient from "./api-client"; import * as defaults from "./defaults.json"; -import { ActionsEnvVars } from "./environment"; +import { ActionsEnvVars, type Env } from "./environment"; import { setUpFeatureFlagTests } from "./feature-flags/testing-util"; import { UnvalidatedObject, validateSchema } from "./json"; import { makeFromSchema } from "./json/testing-util"; @@ -27,6 +27,7 @@ import { initAllState, makeMacro, makeTestToken, + mockApiDetails, RecordingLogger, SAMPLE_DOTCOM_API_DETAILS, setupTests, @@ -699,18 +700,23 @@ test("getProxyPackage - includes platform in name", (t) => { } }); -function mockGetApiClient(endpoints: any) { - return ( - sinon - .stub(apiClient, "getApiClient") - // eslint-disable-next-line @typescript-eslint/no-unsafe-argument - .returns({ rest: endpoints } as any) - ); +function mockGetApiClient(env: Env, endpoints: any) { + const getRequiredInput = mockApiDetails(SAMPLE_DOTCOM_API_DETAILS, env); + const getApiClient = sinon + .stub(apiClient, "getApiClient") + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument + .returns({ rest: endpoints } as any); + + return { getRequiredInput, getApiClient }; } type ReleaseAssets = Array<{ name: string; url?: string }>; -function mockGetReleaseByTag(expectedTag?: string, assets?: ReleaseAssets) { +function mockGetReleaseByTag( + env: Env, + expectedTag?: string, + assets?: ReleaseAssets, +) { const getReleaseByTag = assets === undefined ? sinon.stub().rejects() @@ -731,7 +737,7 @@ function mockGetReleaseByTag(expectedTag?: string, assets?: ReleaseAssets) { return { getReleaseByTag, - getApiClient: mockGetApiClient({ repos: { getReleaseByTag } }), + ...mockGetApiClient(env, { repos: { getReleaseByTag } }), }; } @@ -757,7 +763,6 @@ test.serial( "getDownloadUrl logs unknown platforms/arch and defaults to linux64", async (t) => { const logger = new RecordingLogger(); - mockGetReleaseByTag(); await withTmpDir(async (tempDir) => { const features = mockOfflineFeatures(tempDir, logger); @@ -767,6 +772,7 @@ test.serial( logger, features, }); + mockGetReleaseByTag(state.env); // Stub this to always return `true` so we get the default repo for the tests. sinon.stub(actionsUtil, "isRunningLocalAction").returns(true); @@ -796,11 +802,11 @@ test.serial( "getDownloadUrl returns default bundle URL for linked version", async (t) => { const logger = new RecordingLogger(); - mockGetReleaseByTag(); await withTmpDir(async (tempDir) => { const features = mockOfflineFeatures(tempDir, logger); const state = initAllState({ logger, features }); + mockGetReleaseByTag(state.env); // Stub this to always return `true` so we get the default repo for the tests. sinon.stub(actionsUtil, "isRunningLocalAction").returns(true); @@ -836,7 +842,11 @@ test.serial( sinon.stub(actionsUtil, "isRunningLocalAction").returns(true); for (const assets of testAssets) { - const stubs = mockGetReleaseByTag(defaults.bundleVersion, assets); + const stubs = mockGetReleaseByTag( + state.env, + defaults.bundleVersion, + assets, + ); const info = await startProxyExports.getDownloadUrl( state, SAMPLE_DOTCOM_API_DETAILS, @@ -854,6 +864,7 @@ test.serial( // mockGetReleaseByTag stubs the global getApiClient; since we are calling // that in a loop, we need to manually restore it here stubs.getApiClient.restore(); + stubs.getRequiredInput.restore(); } }); }, @@ -869,7 +880,7 @@ test.serial("getDownloadUrl returns matching release asset", async (t) => { url: "url-we-want", }, ]; - const stubs = mockGetReleaseByTag(defaults.bundleVersion, assets); + const stubs = mockGetReleaseByTag(state.env, defaults.bundleVersion, assets); await withTmpDir(async (tempDir) => { state.env.set(ActionsEnvVars.RUNNER_TEMP, tempDir); @@ -1011,7 +1022,6 @@ test.serial( "getProxyBinaryPath - returns path from tool cache if available", async (t) => { const logger = new RecordingLogger(); - mockGetReleaseByTag(); await withTmpDir(async (tempDir) => { const toolcachePath = "/path/to/proxy/dir"; @@ -1021,8 +1031,11 @@ test.serial( sinon.stub(actionsUtil, "isRunningLocalAction").returns(true); const features = mockOfflineFeatures(tempDir, logger); + const state = initAllState({ logger, features }); + mockGetReleaseByTag(state.env); + const path = await startProxyExports.getProxyBinaryPath( - initAllState({ logger, features }), + state, SAMPLE_DOTCOM_API_DETAILS, ); @@ -1041,7 +1054,7 @@ test.serial( const logger = new RecordingLogger(); const state = initAllState({ logger }); const downloadUrl = "url-we-want"; - const stubs = mockGetReleaseByTag(defaults.bundleVersion, [ + const stubs = mockGetReleaseByTag(state.env, defaults.bundleVersion, [ { name: startProxyExports.getProxyPackage(getTestPlatform(state)), url: downloadUrl, @@ -1091,7 +1104,7 @@ test.serial( ); checkExpectedLogMessages(t, logger.messages, [ - `Found private registry proxy ${startProxyExports.getProxyPackage(getTestPlatform(state))} in test/repo on https://github.com with URL ${downloadUrl}`, + `Found private registry proxy ${startProxyExports.getProxyPackage(getTestPlatform(state))} in test/repo on https://api.github.com with URL ${downloadUrl}`, ]); }, ); @@ -1122,7 +1135,7 @@ test.serial( headers: {}, url: "GET /repos/:owner/:repo/releases/tags/:tag", }); - mockGetApiClient({ repos: { getReleaseByTag } }); + mockGetApiClient(state.env, { repos: { getReleaseByTag } }); await withTmpDir(async (tempDir) => { const toolcachePath = "/path/to/proxy/dir"; @@ -1187,7 +1200,7 @@ test.serial( }); checkExpectedLogMessages(t, logger.messages, [ - `Found private registry proxy ${startProxyExports.getProxyPackage(getTestPlatform(state))} in test/repo on https://github.com with URL url-we-want.`, + `Found private registry proxy ${startProxyExports.getProxyPackage(getTestPlatform(state))} in test/repo on https://api.github.com with URL url-we-want.`, ]); }, ); diff --git a/src/testing-utils.ts b/src/testing-utils.ts index 8c38187c4e..2b320b3463 100644 --- a/src/testing-utils.ts +++ b/src/testing-utils.ts @@ -14,6 +14,7 @@ import * as sinon from "sinon"; import { ActionState, StateFeature } from "./action-common"; import { ActionsEnv, getActionVersion } from "./actions-util"; +import * as actionsUtil from "./actions-util"; import { AnalysisKind } from "./analyses"; import * as apiClient from "./api-client"; import { GitHubApiDetails } from "./api-client"; @@ -52,6 +53,23 @@ export const SAMPLE_DOTCOM_API_DETAILS = { apiURL: "https://api.github.com", }; +export function mockApiDetails( + apiDetails: Required, + env: Env = getEnv(), +) { + // This is a workaround to mock `api.getApiDetails()` since it doesn't seem to be possible to + // mock this directly. The difficulty is that `getApiDetails()` is called locally in + // `api-client.ts`, but `sinon.stub(api, "getApiDetails")` only affects calls to + // `getApiDetails()` via an imported `api` module. + const getRequiredInput = sinon.stub(actionsUtil, "getRequiredInput"); + getRequiredInput.withArgs("token").returns(apiDetails.auth); + + env.set("GITHUB_SERVER_URL", apiDetails.url); + env.set("GITHUB_API_URL", apiDetails.apiURL ?? ""); + + return getRequiredInput; +} + export const LINKED_CLI_VERSION = { cliVersion: defaults.cliVersion, tagName: defaults.bundleVersion, From b2bf75026d97ab337782401fa0d120f524f5541e Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Wed, 14 Oct 2026 11:44:14 +0100 Subject: [PATCH 3/4] Include `apiDetails` in `CodeQLDownloadSource` --- lib/entry-points.js | 19 ++++++------------- src/codeql-bundle.ts | 3 +++ src/setup-codeql.test.ts | 7 ++++++- src/setup-codeql.ts | 15 +++------------ 4 files changed, 18 insertions(+), 26 deletions(-) diff --git a/lib/entry-points.js b/lib/entry-points.js index 9707f1c661..491ace4fbc 100644 --- a/lib/entry-points.js +++ b/lib/entry-points.js @@ -176665,7 +176665,8 @@ async function getCodeQLSource(toolsInput, defaultCliVersion, rawLanguages, othe cliVersion: cliVersion2, compressionMethod, sourceType: "download", - toolsVersion: resolvedVersion ?? "unknown" + toolsVersion: resolvedVersion ?? "unknown", + apiDetails }; } async function tryGetFallbackToolcacheVersion(cliVersion2, tagName, logger2) { @@ -176679,7 +176680,7 @@ async function tryGetFallbackToolcacheVersion(cliVersion2, tagName, logger2) { ); return fallbackVersion; } -var downloadCodeQL = async function(source, apiDetails, tarVersion, tempDir, logger2) { +var downloadCodeQL = async function(source, tarVersion, tempDir, logger2) { const { bundle, compressionMethod } = source; const codeqlURL = bundle.url; const parsedCodeQLURL = new URL(codeqlURL); @@ -176693,7 +176694,7 @@ var downloadCodeQL = async function(source, apiDetails, tarVersion, tempDir, log } else { authorization = getAuthorizationHeaderFor( logger2, - apiDetails, + source.apiDetails, codeqlURL ); } @@ -176817,7 +176818,6 @@ async function setupCodeQLBundle(toolsInput, apiDetails, tempDir, variant, defau const result = await downloadCodeQLBundle( { env: getEnv(), features, logger: logger2 }, source, - apiDetails, zstdAvailability.version, tempDir ); @@ -176837,19 +176837,13 @@ async function setupCodeQLBundle(toolsInput, apiDetails, tempDir, variant, defau toolsVersion: source.toolsVersion }; } -async function downloadCodeQLBundle(action, source, apiDetails, tarVersion, tempDir) { +async function downloadCodeQLBundle(action, source, tarVersion, tempDir) { const { bundle } = source; const { logger: logger2 } = action; await tryDeleteToolcacheBundles(action); const startTime = import_perf_hooks4.performance.now(); try { - return await downloadCodeQL( - source, - apiDetails, - tarVersion, - tempDir, - logger2 - ); + return await downloadCodeQL(source, tarVersion, tempDir, logger2); } catch (e) { if (bundle.kind !== "per-language" || bundle.combinedBundleURL === void 0 || asHTTPError(e)?.status !== 404) { throw e; @@ -176860,7 +176854,6 @@ async function downloadCodeQLBundle(action, source, apiDetails, tarVersion, temp ...source, bundle: { kind: "combined", url: bundle.combinedBundleURL } }, - apiDetails, tarVersion, tempDir, logger2 diff --git a/src/codeql-bundle.ts b/src/codeql-bundle.ts index 2d262c848e..2b6da00291 100644 --- a/src/codeql-bundle.ts +++ b/src/codeql-bundle.ts @@ -1,3 +1,4 @@ +import type { GitHubApiDetails } from "./github"; import { BuiltInLanguage, parseBuiltInLanguage } from "./languages"; import { BundlePlatform } from "./platform"; import type { CompressionMethod } from "./tar"; @@ -17,6 +18,8 @@ export type CodeQLBundle = export interface CodeQLDownloadSource { /** Distinguishes downloads from local archives and cached installations. */ sourceType: "download"; + /** Information about the GitHub instance that contains the asset. */ + apiDetails: GitHubApiDetails; /** The bundle to download. */ bundle: CodeQLBundle; /** The compression format of the bundle archive. */ diff --git a/src/setup-codeql.test.ts b/src/setup-codeql.test.ts index 9ad2c6773f..d8c6f32d6e 100644 --- a/src/setup-codeql.test.ts +++ b/src/setup-codeql.test.ts @@ -156,6 +156,7 @@ test.serial( compressionMethod: "gzip", sourceType: "download", toolsVersion: "1.2.3", + apiDetails: SAMPLE_DOTCOM_API_DETAILS, } satisfies setupCodeql.CodeQLDownloadSource); }); }, @@ -435,6 +436,7 @@ test.serial( compressionMethod: "zstd", sourceType: "download", toolsVersion: expectedVersion, + apiDetails: SAMPLE_DOTCOM_API_DETAILS, } satisfies setupCodeql.CodeQLToolsSource); const result = await setupCodeql.setupCodeQLBundle( @@ -508,6 +510,7 @@ test.serial( compressionMethod: "zstd", sourceType: "download", toolsVersion: expectedVersion, + apiDetails: SAMPLE_DOTCOM_API_DETAILS, } satisfies setupCodeql.CodeQLToolsSource); // Afterwards, ensure that we see the expected messages in the log. @@ -655,6 +658,7 @@ for (const toolsInput of ["nightly", "nightly-latest"]) { cliVersion: undefined, compressionMethod: "zstd", toolsVersion: "0.0.0-30260213", + apiDetails: SAMPLE_DOTCOM_API_DETAILS, } satisfies setupCodeql.CodeQLDownloadSource); t.true(latestNightlyRequest.calledOnce); }); @@ -807,6 +811,7 @@ for (const date of ["20200101", "30260213"]) { cliVersion: undefined, compressionMethod: "zstd", toolsVersion: `0.0.0-${date}`, + apiDetails: SAMPLE_DOTCOM_API_DETAILS, } satisfies setupCodeql.CodeQLDownloadSource); await t.throwsAsync( @@ -1864,8 +1869,8 @@ async function runDownloadCodeQL( cliVersion: CLEANUP_CLI_VERSION, sourceType: "download", toolsVersion: CLEANUP_CLI_VERSION, + apiDetails: SAMPLE_DOTCOM_API_DETAILS, }, - SAMPLE_DOTCOM_API_DETAILS, undefined, // tarVersion toolcacheRoot, // tempDir ); diff --git a/src/setup-codeql.ts b/src/setup-codeql.ts index 07666d6302..6668ef1e7b 100644 --- a/src/setup-codeql.ts +++ b/src/setup-codeql.ts @@ -718,6 +718,7 @@ export async function getCodeQLSource( compressionMethod, sourceType: "download", toolsVersion: resolvedVersion ?? "unknown", + apiDetails, }; } @@ -746,7 +747,6 @@ async function tryGetFallbackToolcacheVersion( // be able to stub this function and have other functions in this file use that stub. export const downloadCodeQL = async function ( source: CodeQLDownloadSource, - apiDetails: api.GitHubApiDetails, tarVersion: tar.TarVersion | undefined, tempDir: string, logger: Logger, @@ -769,7 +769,7 @@ export const downloadCodeQL = async function ( } else { authorization = api.getAuthorizationHeaderFor( logger, - apiDetails, + source.apiDetails, codeqlURL, ); } @@ -985,7 +985,6 @@ export async function setupCodeQLBundle( const result = await downloadCodeQLBundle( { env: getEnv(), features, logger }, source, - apiDetails, zstdAvailability.version, tempDir, ); @@ -1021,7 +1020,6 @@ export async function setupCodeQLBundle( export async function downloadCodeQLBundle( action: ActionState<["Logger", "ReadOnlyEnv", "FeatureFlags"]>, source: CodeQLDownloadSource, - apiDetails: api.GitHubApiDetails, tarVersion: tar.TarVersion | undefined, tempDir: string, ): Promise<{ @@ -1035,13 +1033,7 @@ export async function downloadCodeQLBundle( const startTime = performance.now(); try { - return await downloadCodeQL( - source, - apiDetails, - tarVersion, - tempDir, - logger, - ); + return await downloadCodeQL(source, tarVersion, tempDir, logger); } catch (e) { if ( bundle.kind !== "per-language" || @@ -1057,7 +1049,6 @@ export async function downloadCodeQLBundle( ...source, bundle: { kind: "combined", url: bundle.combinedBundleURL }, }, - apiDetails, tarVersion, tempDir, logger, From 8884b5b42398480fefafb763fcb1629864876f8a Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Wed, 14 Oct 2026 12:02:00 +0100 Subject: [PATCH 4/4] Add `InstanceApiClient` and incorporate `getAuthorizationHeaderFor` into it --- lib/entry-points.js | 54 +++++++++++++++--------- src/api-client.test.ts | 87 ++++++++++++++------------------------ src/api-client.ts | 91 ++++++++++++++++++++++++---------------- src/setup-codeql.test.ts | 13 ++++-- src/setup-codeql.ts | 7 +--- src/start-proxy.test.ts | 20 ++++++++- src/start-proxy.ts | 8 ++-- 7 files changed, 155 insertions(+), 125 deletions(-) diff --git a/lib/entry-points.js b/lib/entry-points.js index 491ace4fbc..52f8e10422 100644 --- a/lib/entry-points.js +++ b/lib/entry-points.js @@ -170738,6 +170738,37 @@ function makeProxyRequestOptions(dispatcher) { } }; } +var InstanceApiClient = class { + client; + apiDetails; + authorizationHeader; + constructor(apiDetails, clientOptions = {}) { + this.apiDetails = apiDetails; + this.client = createApiClientWithDetails(apiDetails, clientOptions); + const token = this.apiDetails.auth?.trim(); + if (token !== void 0 && token !== "") { + this.authorizationHeader = `token ${token}`; + } + } + /** + * Gets a value for the `Authorization` header for a request to `url`; or `undefined` if the + * `Authorization` header should not be set for `url`. + * + * @param logger The logger to use for debugging messages. + * @param url The URL for which we want to add an `Authorization` header. + */ + getAuthorizationHeaderFor(logger2, url2) { + if (this.authorizationHeader === void 0) { + return void 0; + } + if (url2.startsWith(`${this.apiDetails.url}/`) || this.apiDetails.apiURL && url2.startsWith(`${this.apiDetails.apiURL}/`)) { + logger2.debug(`Providing an authorization token.`); + return this.authorizationHeader; + } + logger2.debug(`Not using an authorization token.`); + return void 0; + } +}; function createApiClientWithDetails(apiDetails, { allowExternal = false, proxy = void 0 } = {}) { const auth2 = allowExternal && apiDetails.externalRepoAuth !== void 0 ? apiDetails.externalRepoAuth : apiDetails.auth; const retryingOctokit = githubUtils.GitHub.plugin(retry); @@ -170781,18 +170812,6 @@ function getDotcomApiDetails(env) { function getApiClientWithExternalAuth(apiDetails, proxy) { return createApiClientWithDetails(apiDetails, { allowExternal: true, proxy }); } -function getAuthorizationHeaderFor(logger2, apiDetails, url2) { - const token = apiDetails.auth?.trim(); - if (token === void 0 || token === "") { - return void 0; - } - if (url2.startsWith(`${apiDetails.url}/`) || apiDetails.apiURL && url2.startsWith(`${apiDetails.apiURL}/`)) { - logger2.debug(`Providing an authorization token.`); - return `token ${token}`; - } - logger2.debug(`Not using an authorization token.`); - return void 0; -} var cachedGitHubVersion = void 0; async function getGitHubVersionFromApi(apiClient, apiDetails) { if (parseGitHubUrl(apiDetails.url) === GITHUB_DOTCOM_URL) { @@ -176692,11 +176711,8 @@ var downloadCodeQL = async function(source, tarVersion, tempDir, logger2) { if (searchParams.has("token")) { logger2.debug("CodeQL tools URL contains an authorization token."); } else { - authorization = getAuthorizationHeaderFor( - logger2, - source.apiDetails, - codeqlURL - ); + const client = new InstanceApiClient(source.apiDetails); + authorization = client.getAuthorizationHeaderFor(logger2, codeqlURL); } const toolcacheDestination = getToolcacheDestination({ logger: logger2 }, source); const extractedBundlePath = toolcacheDestination.orElse( @@ -187927,6 +187943,7 @@ async function getProxyBinaryPath(action, apiDetails) { const logger2 = action.logger; logger2.startGroup("Setting up authentication proxy"); try { + const client = new InstanceApiClient(apiDetails); const proxyFileName = getProxyFilename(); const proxyInfo = await getDownloadUrl(action, apiDetails); logger2.info( @@ -187937,9 +187954,8 @@ async function getProxyBinaryPath(action, apiDetails) { logger2.info( `Authentication proxy is not in the toolcache. Will try to download it from '${proxyInfo.url}'.` ); - const authorization = getAuthorizationHeaderFor( + const authorization = client.getAuthorizationHeaderFor( logger2, - apiDetails, proxyInfo.url ); const temp = await downloadProxy(logger2, proxyInfo.url, authorization); diff --git a/src/api-client.test.ts b/src/api-client.test.ts index 2bb878f3b7..54a98e74a6 100644 --- a/src/api-client.test.ts +++ b/src/api-client.test.ts @@ -308,70 +308,45 @@ test.serial( test("getAuthorizationHeaderFor - returns undefined if there's no token", (t) => { const logger = new RecordingLogger(); - t.is( - api.getAuthorizationHeaderFor( - logger, - { ...SAMPLE_DOTCOM_API_DETAILS, auth: undefined }, - `${GITHUB_DOTCOM_URL}/github`, - ), - undefined, - ); - t.is( - api.getAuthorizationHeaderFor( - logger, - { ...SAMPLE_DOTCOM_API_DETAILS, auth: "" }, - `${GITHUB_DOTCOM_URL}/github`, - ), - undefined, - ); - t.is( - api.getAuthorizationHeaderFor( - logger, - { ...SAMPLE_DOTCOM_API_DETAILS, auth: " " }, - `${GITHUB_DOTCOM_URL}/github`, - ), - undefined, - ); + for (const authValue of [undefined, "", " "]) { + const client = new api.InstanceApiClient({ + ...SAMPLE_DOTCOM_API_DETAILS, + auth: authValue, + }); + t.is( + client.getAuthorizationHeaderFor(logger, `${GITHUB_DOTCOM_URL}/github`), + undefined, + ); + } }); test("getAuthorizationHeaderFor - returns header value with token if url is allowed", (t) => { const logger = new RecordingLogger(); + const client = new api.InstanceApiClient({ + ...SAMPLE_DOTCOM_API_DETAILS, + }); - t.is( - api.getAuthorizationHeaderFor( - logger, - SAMPLE_DOTCOM_API_DETAILS, - `${GITHUB_DOTCOM_URL}/github`, - ), - `token ${SAMPLE_DOTCOM_API_DETAILS.auth}`, - ); - t.is( - api.getAuthorizationHeaderFor( - logger, - SAMPLE_DOTCOM_API_DETAILS, - `${GITHUB_DOTCOM_API_URL}/github`, - ), - `token ${SAMPLE_DOTCOM_API_DETAILS.auth}`, - ); + for (const url of [ + `${GITHUB_DOTCOM_URL}/github`, + `${GITHUB_DOTCOM_API_URL}/github`, + ]) { + t.is( + client.getAuthorizationHeaderFor(logger, url), + `token ${SAMPLE_DOTCOM_API_DETAILS.auth}`, + ); + } }); test("getAuthorizationHeaderFor - returns undefined if url is not allowed", (t) => { const logger = new RecordingLogger(); + const client = new api.InstanceApiClient({ + ...SAMPLE_DOTCOM_API_DETAILS, + }); - t.is( - api.getAuthorizationHeaderFor( - logger, - SAMPLE_DOTCOM_API_DETAILS, - `https://localhost/github`, - ), - undefined, - ); - t.is( - api.getAuthorizationHeaderFor( - logger, - SAMPLE_DOTCOM_API_DETAILS, - `https://api.localhost/github`, - ), - undefined, - ); + for (const url of [ + `https://localhost/github`, + `https://api.localhost/github`, + ]) { + t.is(client.getAuthorizationHeaderFor(logger, url), undefined); + } }); diff --git a/src/api-client.ts b/src/api-client.ts index 6dd53e47ef..c37414f3ef 100644 --- a/src/api-client.ts +++ b/src/api-client.ts @@ -122,6 +122,61 @@ export function makeProxyRequestOptions( /** The type of GitHub API client we use. */ export type ApiClient = InstanceType; +/** + * Encapsulates a {@link GitHubApiCombinedDetails} along with operations which ensure that any credentials + * are only used with the GitHub instance that is represented by the {@link GitHubApiCombinedDetails} value. + */ +export class InstanceApiClient { + private readonly client: ApiClient; + private readonly apiDetails: GitHubApiCombinedDetails; + private readonly authorizationHeader: string | undefined; + + constructor( + apiDetails: GitHubApiCombinedDetails, + clientOptions: CreateApiClientOptions = {}, + ) { + this.apiDetails = apiDetails; + this.client = createApiClientWithDetails(apiDetails, clientOptions); + + // Compute the Authorization header value if we have a token. + const token = this.apiDetails.auth?.trim(); + if (token !== undefined && token !== "") { + this.authorizationHeader = `token ${token}`; + } + } + + /** + * Gets a value for the `Authorization` header for a request to `url`; or `undefined` if the + * `Authorization` header should not be set for `url`. + * + * @param logger The logger to use for debugging messages. + * @param url The URL for which we want to add an `Authorization` header. + */ + public getAuthorizationHeaderFor( + logger: Logger, + url: string, + ): string | undefined { + // We don't have to check the `url` if we don't have a token to begin with. + if (this.authorizationHeader === undefined) { + return undefined; + } + + // We only want to provide an authorization header if we are downloading + // from the same GitHub instance the Action is running on. + // This avoids leaking Enterprise tokens to dotcom. + if ( + url.startsWith(`${this.apiDetails.url}/`) || + (this.apiDetails.apiURL && url.startsWith(`${this.apiDetails.apiURL}/`)) + ) { + logger.debug(`Providing an authorization token.`); + return this.authorizationHeader; + } + + logger.debug(`Not using an authorization token.`); + return undefined; + } +} + /** Options for `createApiClientWithDetails`. */ interface CreateApiClientOptions { allowExternal?: boolean; @@ -218,42 +273,6 @@ export function getApiClientWithExternalAuth( return createApiClientWithDetails(apiDetails, { allowExternal: true, proxy }); } -/** - * Gets a value for the `Authorization` header for a request to `url`; or `undefined` if the - * `Authorization` header should not be set for `url`. - * - * @param logger The logger to use for debugging messages. - * @param apiDetails Details of the GitHub API we are using. - * @param url The URL for which we want to add an `Authorization` header. - * - * @returns The value for the `Authorization` header or `undefined` if it shouldn't be populated. - */ -export function getAuthorizationHeaderFor( - logger: Logger, - apiDetails: GitHubApiDetails, - url: string, -): string | undefined { - // We don't have to check the `url` if we don't have a token to begin with. - const token = apiDetails.auth?.trim(); - if (token === undefined || token === "") { - return undefined; - } - - // We only want to provide an authorization header if we are downloading - // from the same GitHub instance the Action is running on. - // This avoids leaking Enterprise tokens to dotcom. - if ( - url.startsWith(`${apiDetails.url}/`) || - (apiDetails.apiURL && url.startsWith(`${apiDetails.apiURL}/`)) - ) { - logger.debug(`Providing an authorization token.`); - return `token ${token}`; - } - - logger.debug(`Not using an authorization token.`); - return undefined; -} - let cachedGitHubVersion: GitHubVersion | undefined = undefined; export async function getGitHubVersionFromApi( diff --git a/src/setup-codeql.test.ts b/src/setup-codeql.test.ts index d8c6f32d6e..875cbcbf1d 100644 --- a/src/setup-codeql.test.ts +++ b/src/setup-codeql.test.ts @@ -1290,7 +1290,14 @@ for (const fallback of [false, true]) { request: { fetch: fetchRelease }, }); sinon.stub(api, "getApiClient").value(() => client); - const authorizationSpy = sinon.spy(api, "getAuthorizationHeaderFor"); + + const spyClient = new api.InstanceApiClient(SAMPLE_DOTCOM_API_DETAILS); + const authorizationSpy = sinon.spy( + spyClient, + "getAuthorizationHeaderFor", + ); + sinon.stub(api, "InstanceApiClient").returns(spyClient); + const extractStub = stubDownloadAndExtract(); if (fallback) { extractStub.onFirstCall().rejects(new HTTPError("Not Found", 404)); @@ -1323,9 +1330,9 @@ for (const fallback of [false, true]) { t.is(extractStub.firstCall.args[0], assetURL); t.is(extractStub.lastCall.args[0], fallback ? combinedURL : assetURL); t.is(authorizationSpy.callCount, extractStub.callCount); - t.is(authorizationSpy.firstCall.args[2], assetURL); + t.is(authorizationSpy.firstCall.args[1], assetURL); t.is( - authorizationSpy.lastCall.args[2], + authorizationSpy.lastCall.args[1], fallback ? combinedURL : assetURL, ); t.is(extractStub.lastCall.args[3], "token token"); diff --git a/src/setup-codeql.ts b/src/setup-codeql.ts index 6668ef1e7b..5186405fca 100644 --- a/src/setup-codeql.ts +++ b/src/setup-codeql.ts @@ -767,11 +767,8 @@ export const downloadCodeQL = async function ( if (searchParams.has("token")) { logger.debug("CodeQL tools URL contains an authorization token."); } else { - authorization = api.getAuthorizationHeaderFor( - logger, - source.apiDetails, - codeqlURL, - ); + const client = new api.InstanceApiClient(source.apiDetails); + authorization = client.getAuthorizationHeaderFor(logger, codeqlURL); } const toolcacheDestination = getToolcacheDestination({ logger }, source); diff --git a/src/start-proxy.test.ts b/src/start-proxy.test.ts index f432b9b16d..2b860ba971 100644 --- a/src/start-proxy.test.ts +++ b/src/start-proxy.test.ts @@ -1023,6 +1023,8 @@ test.serial( async (t) => { const logger = new RecordingLogger(); + sinon.stub(apiClient, "getApiDetails").returns(SAMPLE_DOTCOM_API_DETAILS); + await withTmpDir(async (tempDir) => { const toolcachePath = "/path/to/proxy/dir"; sinon.stub(toolcache, "find").returns(toolcachePath); @@ -1063,9 +1065,16 @@ test.serial( const toolcachePath = "/path/to/proxy/dir"; const find = sinon.stub(toolcache, "find").returns(""); + const apiDetails = { + auth: "", + url: "", + apiURL: "", + } satisfies apiClient.GitHubApiDetails; + const client = new apiClient.InstanceApiClient(apiDetails); const getAuthorizationHeaderFor = sinon - .stub(apiClient, "getAuthorizationHeaderFor") + .stub(client, "getAuthorizationHeaderFor") .returns(undefined); + sinon.stub(apiClient, "InstanceApiClient").returns(client); const archivePath = "/path/to/archive"; const downloadTool = sinon .stub(toolcache, "downloadTool") @@ -1140,9 +1149,16 @@ test.serial( await withTmpDir(async (tempDir) => { const toolcachePath = "/path/to/proxy/dir"; const find = sinon.stub(toolcache, "find").returns(""); + const apiDetails = { + auth: "", + url: "", + apiURL: "", + } satisfies apiClient.GitHubApiDetails; + const client = new apiClient.InstanceApiClient(apiDetails); const getAuthorizationHeaderFor = sinon - .stub(apiClient, "getAuthorizationHeaderFor") + .stub(client, "getAuthorizationHeaderFor") .returns(undefined); + sinon.stub(apiClient, "InstanceApiClient").returns(client); const archivePath = "/path/to/archive"; const downloadTool = sinon .stub(toolcache, "downloadTool") diff --git a/src/start-proxy.ts b/src/start-proxy.ts index c3826ed6e3..f4d59a4f79 100644 --- a/src/start-proxy.ts +++ b/src/start-proxy.ts @@ -5,9 +5,9 @@ import * as toolcache from "@actions/tool-cache"; import { ActionState } from "./action-common"; import { - getAuthorizationHeaderFor, getGitHubVersion, - GitHubApiDetails, + InstanceApiClient, + type GitHubApiDetails, } from "./api-client"; import * as artifactScanner from "./artifact-scanner"; import { Config } from "./config-utils"; @@ -545,6 +545,7 @@ export async function getProxyBinaryPath( logger.startGroup("Setting up authentication proxy"); try { + const client = new InstanceApiClient(apiDetails); const proxyFileName = getProxyFilename(); const proxyInfo = await getDownloadUrl(action, apiDetails); @@ -559,9 +560,8 @@ export async function getProxyBinaryPath( `Authentication proxy is not in the toolcache. Will try to download it from '${proxyInfo.url}'.`, ); - const authorization = getAuthorizationHeaderFor( + const authorization = client.getAuthorizationHeaderFor( logger, - apiDetails, proxyInfo.url, ); const temp = await downloadProxy(logger, proxyInfo.url, authorization);