From 909fda1468f8ef25624848914b92c3faeba7cfe7 Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Tue, 6 Oct 2026 13:36:35 +0100 Subject: [PATCH 01/25] Allow logging functions to accept `string[]` directly --- lib/entry-points.js | 20 ++++++++++++++++---- src/logging.test.ts | 17 +++++++++++++++++ src/logging.ts | 43 +++++++++++++++++++++++++++++++++++-------- src/testing-utils.ts | 14 +++++++------- 4 files changed, 75 insertions(+), 19 deletions(-) create mode 100644 src/logging.test.ts diff --git a/lib/entry-points.js b/lib/entry-points.js index ad7ac988a1..1a75678e97 100644 --- a/lib/entry-points.js +++ b/lib/entry-points.js @@ -170435,12 +170435,24 @@ function fixCodeQualityCategory(logger2, category, env = getEnv()) { // src/logging.ts var core4 = __toESM(require_core()); +var joinMessageStrings = (message) => { + if (Array.isArray(message)) { + return message.join(" "); + } else { + return message; + } +}; +var autoJoinMessageStrings = (fn) => { + return (message) => { + return fn(joinMessageStrings(message)); + }; +}; function getActionsLogger() { return { - debug: core4.debug, - info: core4.info, - warning: core4.warning, - error: core4.error, + debug: autoJoinMessageStrings(core4.debug), + info: autoJoinMessageStrings(core4.info), + warning: autoJoinMessageStrings(core4.warning), + error: autoJoinMessageStrings(core4.error), isDebug: core4.isDebug, startGroup: core4.startGroup, endGroup: core4.endGroup diff --git a/src/logging.test.ts b/src/logging.test.ts new file mode 100644 index 0000000000..33f6f8c162 --- /dev/null +++ b/src/logging.test.ts @@ -0,0 +1,17 @@ +import test from "ava"; + +import { joinMessageStrings } from "./logging"; +import { setupTests } from "./testing-utils"; + +setupTests(test); + +test("joinMessageStrings", async (t) => { + // For strings and errors, it is the identity function. + t.deepEqual(joinMessageStrings("Hello"), "Hello"); + + const error = new Error("Some error"); + t.deepEqual(joinMessageStrings(error), error); + + // For arrays of strings, we get a join-ed string. + t.deepEqual(joinMessageStrings(["foo", "bar"]), "foo bar"); +}); diff --git a/src/logging.ts b/src/logging.ts index 2c34cb54d4..177aca6c7b 100644 --- a/src/logging.ts +++ b/src/logging.ts @@ -1,10 +1,16 @@ import * as core from "@actions/core"; +/** Types that all of our logging functions accept. */ +export type Loggable = string | string[]; + +/** Some logging functions also accept errors directly. */ +export type LoggableError = Loggable | Error; + export interface Logger { - debug: (message: string) => void; - info: (message: string) => void; - warning: (message: string | Error) => void; - error: (message: string | Error) => void; + debug: (message: Loggable) => void; + info: (message: Loggable) => void; + warning: (message: LoggableError) => void; + error: (message: LoggableError) => void; isDebug: () => boolean; @@ -12,12 +18,33 @@ export interface Logger { endGroup: () => void; } +/** If `message` is an array of strings, the array is `join`-ed into one string separated by spaces. */ +export const joinMessageStrings = ( + message: string[] | string | T, +): string | T => { + if (Array.isArray(message)) { + return message.join(" "); + } else { + return message; + } +}; + +/** + * Wraps a logging function so that `joinMessageStrings` is applied to the input before + * calling the wrapped logging function. + */ +const autoJoinMessageStrings = (fn: (msg: string | T) => void) => { + return (message: string[] | string | T) => { + return fn(joinMessageStrings(message)); + }; +}; + export function getActionsLogger(): Logger { return { - debug: core.debug, - info: core.info, - warning: core.warning, - error: core.error, + debug: autoJoinMessageStrings(core.debug), + info: autoJoinMessageStrings(core.info), + warning: autoJoinMessageStrings(core.warning), + error: autoJoinMessageStrings(core.error), isDebug: core.isDebug, startGroup: core.startGroup, endGroup: core.endGroup, diff --git a/src/testing-utils.ts b/src/testing-utils.ts index a5ba50554c..df81f51bb4 100644 --- a/src/testing-utils.ts +++ b/src/testing-utils.ts @@ -30,7 +30,7 @@ import { featureConfig, FeatureEnablement, } from "./feature-flags"; -import { Logger } from "./logging"; +import { joinMessageStrings, Loggable, LoggableError, Logger } from "./logging"; import { OverlayDatabaseMode } from "./overlay/overlay-database-mode"; import { getBundlePlatform } from "./platform"; import { ActionName } from "./status-report"; @@ -622,8 +622,8 @@ export class RecordingLogger implements Logger { constructor(private readonly logToConsole: boolean = true) {} - private addMessage(level: LogLevel, message: string | Error): void { - this.messages.push({ type: level, message }); + private addMessage(level: LogLevel, message: LoggableError): void { + this.messages.push({ type: level, message: joinMessageStrings(message) }); if (this.logToConsole) { // eslint-disable-next-line no-console @@ -648,19 +648,19 @@ export class RecordingLogger implements Logger { return true; } - debug(message: string) { + debug(message: Loggable) { this.addMessage("debug", message); } - info(message: string) { + info(message: Loggable) { this.addMessage("info", message); } - warning(message: string | Error) { + warning(message: LoggableError) { this.addMessage("warning", message); } - error(message: string | Error) { + error(message: LoggableError) { this.addMessage("error", message); } From cb7acf539626ded596e80ac0f4d7c38fc25a89d6 Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Tue, 6 Oct 2026 13:45:35 +0100 Subject: [PATCH 02/25] Refactor `getCodeQLActionRepository` out of `setup-codeql` --- lib/entry-points.js | 22 ++++++++++++---------- src/setup-codeql.test.ts | 18 ------------------ src/setup-codeql.ts | 20 ++++---------------- src/setup/repository.test.ts | 31 +++++++++++++++++++++++++++++++ src/setup/repository.ts | 19 +++++++++++++++++++ 5 files changed, 66 insertions(+), 44 deletions(-) create mode 100644 src/setup/repository.test.ts create mode 100644 src/setup/repository.ts diff --git a/lib/entry-points.js b/lib/entry-points.js index 1a75678e97..f0cc985d11 100644 --- a/lib/entry-points.js +++ b/lib/entry-points.js @@ -175634,6 +175634,18 @@ function logPerLanguageBundleFallback({ logger: logger2 }, language, location) { ); } +// src/setup/repository.ts +var CODEQL_DEFAULT_ACTION_REPOSITORY = "github/codeql-action"; +function getCodeQLActionRepository(logger2) { + if (isRunningLocalAction()) { + logger2.info( + "The CodeQL Action is checked out locally. Using the default CodeQL Action repository." + ); + return CODEQL_DEFAULT_ACTION_REPOSITORY; + } + return getRequiredEnvParam("GITHUB_ACTION_REPOSITORY"); +} + // src/tar.ts var import_child_process = require("child_process"); var fs12 = __toESM(require("fs")); @@ -176025,21 +176037,11 @@ function writeToolcacheMarkerFile(extractedPath, logger2) { } // src/setup-codeql.ts -var CODEQL_DEFAULT_ACTION_REPOSITORY = "github/codeql-action"; var CODEQL_NIGHTLIES_REPOSITORY_OWNER = "dsp-testing"; var CODEQL_NIGHTLIES_REPOSITORY_NAME = "codeql-cli-nightlies"; var CODEQL_BUNDLE_VERSION_ALIAS = ["linked", "latest"]; var CODEQL_NIGHTLY_TOOLS_INPUTS = ["nightly", "nightly-latest"]; var CODEQL_TOOLCACHE_INPUT = "toolcache"; -function getCodeQLActionRepository(logger2) { - if (isRunningLocalAction()) { - logger2.info( - "The CodeQL Action is checked out locally. Using the default CodeQL Action repository." - ); - return CODEQL_DEFAULT_ACTION_REPOSITORY; - } - return getRequiredEnvParam("GITHUB_ACTION_REPOSITORY"); -} async function getCodeQLBundleDownloadURL(tagName, apiDetails, codeQLBundleName, logger2) { const codeQLActionRepository = getCodeQLActionRepository(logger2); const potentialDownloadSources = [ diff --git a/src/setup-codeql.test.ts b/src/setup-codeql.test.ts index c33ac0700a..e7a93455fe 100644 --- a/src/setup-codeql.test.ts +++ b/src/setup-codeql.test.ts @@ -126,24 +126,6 @@ test.serial("convert to semver", (t) => { } }); -test.serial("getCodeQLActionRepository", (t) => { - const logger = getRunnerLogger(true); - - initializeEnvironment("1.2.3"); - - // isRunningLocalAction() === true - delete process.env["GITHUB_ACTION_REPOSITORY"]; - process.env["RUNNER_TEMP"] = path.dirname(__dirname); - const repoLocalRunner = setupCodeql.getCodeQLActionRepository(logger); - t.deepEqual(repoLocalRunner, "github/codeql-action"); - - // isRunningLocalAction() === false - sinon.stub(actionsUtil, "isRunningLocalAction").returns(false); - process.env["GITHUB_ACTION_REPOSITORY"] = "xxx/yyy"; - const repoEnv = setupCodeql.getCodeQLActionRepository(logger); - t.deepEqual(repoEnv, "xxx/yyy"); -}); - test.serial( "getCodeQLSource sets CLI version for a semver tagged bundle", async (t) => { diff --git a/src/setup-codeql.ts b/src/setup-codeql.ts index d306d40d63..793191b8b1 100644 --- a/src/setup-codeql.ts +++ b/src/setup-codeql.ts @@ -14,7 +14,6 @@ import { isAnalyzingPullRequest, isDynamicWorkflow, isGitHubHostedRunner, - isRunningLocalAction, } from "./actions-util"; import * as api from "./api-client"; import { @@ -45,6 +44,10 @@ import { logPerLanguageBundleFallback, } from "./per-language-bundles"; import { getBundlePlatform } from "./platform"; +import { + CODEQL_DEFAULT_ACTION_REPOSITORY, + getCodeQLActionRepository, +} from "./setup/repository"; import * as tar from "./tar"; import { deleteToolcacheBundles, @@ -66,7 +69,6 @@ export enum ToolsSource { Download = "DOWNLOAD", } -const CODEQL_DEFAULT_ACTION_REPOSITORY = "github/codeql-action"; const CODEQL_NIGHTLIES_REPOSITORY_OWNER = "dsp-testing"; const CODEQL_NIGHTLIES_REPOSITORY_NAME = "codeql-cli-nightlies"; @@ -74,20 +76,6 @@ const CODEQL_BUNDLE_VERSION_ALIAS: string[] = ["linked", "latest"]; const CODEQL_NIGHTLY_TOOLS_INPUTS = ["nightly", "nightly-latest"]; const CODEQL_TOOLCACHE_INPUT = "toolcache"; -export function getCodeQLActionRepository(logger: Logger): string { - if (isRunningLocalAction()) { - // This handles the case where the Action does not come from an Action repository, - // e.g. our integration tests which use the Action code from the current checkout. - // In these cases, the GITHUB_ACTION_REPOSITORY environment variable is not set. - logger.info( - "The CodeQL Action is checked out locally. Using the default CodeQL Action repository.", - ); - return CODEQL_DEFAULT_ACTION_REPOSITORY; - } - - return util.getRequiredEnvParam("GITHUB_ACTION_REPOSITORY"); -} - async function getCodeQLBundleDownloadURL( tagName: string, apiDetails: api.GitHubApiDetails, diff --git a/src/setup/repository.test.ts b/src/setup/repository.test.ts new file mode 100644 index 0000000000..c3dccbf9c2 --- /dev/null +++ b/src/setup/repository.test.ts @@ -0,0 +1,31 @@ +import * as path from "path"; + +import test from "ava"; +import * as sinon from "sinon"; + +import * as actionsUtil from "../actions-util"; +import { getRunnerLogger } from "../logging"; +import { setupTests } from "../testing-utils"; +import { initializeEnvironment } from "../util"; + +import { getCodeQLActionRepository } from "./repository"; + +setupTests(test); + +test.serial("getCodeQLActionRepository", (t) => { + const logger = getRunnerLogger(true); + + initializeEnvironment("1.2.3"); + + // isRunningLocalAction() === true + delete process.env["GITHUB_ACTION_REPOSITORY"]; + process.env["RUNNER_TEMP"] = path.dirname(__dirname); + const repoLocalRunner = getCodeQLActionRepository(logger); + t.deepEqual(repoLocalRunner, "github/codeql-action"); + + // isRunningLocalAction() === false + sinon.stub(actionsUtil, "isRunningLocalAction").returns(false); + process.env["GITHUB_ACTION_REPOSITORY"] = "xxx/yyy"; + const repoEnv = getCodeQLActionRepository(logger); + t.deepEqual(repoEnv, "xxx/yyy"); +}); diff --git a/src/setup/repository.ts b/src/setup/repository.ts new file mode 100644 index 0000000000..908cc39ee7 --- /dev/null +++ b/src/setup/repository.ts @@ -0,0 +1,19 @@ +import { isRunningLocalAction } from "../actions-util"; +import { Logger } from "../logging"; +import * as util from "../util"; + +export const CODEQL_DEFAULT_ACTION_REPOSITORY = "github/codeql-action"; + +export function getCodeQLActionRepository(logger: Logger): string { + if (isRunningLocalAction()) { + // This handles the case where the Action does not come from an Action repository, + // e.g. our integration tests which use the Action code from the current checkout. + // In these cases, the GITHUB_ACTION_REPOSITORY environment variable is not set. + logger.info( + "The CodeQL Action is checked out locally. Using the default CodeQL Action repository.", + ); + return CODEQL_DEFAULT_ACTION_REPOSITORY; + } + + return util.getRequiredEnvParam("GITHUB_ACTION_REPOSITORY"); +} From 3f57a005543dac0b43f0fa212b72e5c09612cfa0 Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Tue, 6 Oct 2026 14:00:12 +0100 Subject: [PATCH 03/25] Use `ActionState` for `getCodeQLActionRepository` --- lib/entry-points.js | 13 ++++++++----- src/actions-util.ts | 4 ++-- src/setup-codeql.ts | 5 ++++- src/setup/repository.test.ts | 27 +++++++++++++++------------ src/setup/repository.ts | 23 +++++++++++++++++------ 5 files changed, 46 insertions(+), 26 deletions(-) diff --git a/lib/entry-points.js b/lib/entry-points.js index f0cc985d11..3a4bdcb05c 100644 --- a/lib/entry-points.js +++ b/lib/entry-points.js @@ -175636,14 +175636,14 @@ function logPerLanguageBundleFallback({ logger: logger2 }, language, location) { // src/setup/repository.ts var CODEQL_DEFAULT_ACTION_REPOSITORY = "github/codeql-action"; -function getCodeQLActionRepository(logger2) { - if (isRunningLocalAction()) { - logger2.info( +function getCodeQLActionRepository(action) { + if (isRunningLocalAction(action.env)) { + action.logger.info( "The CodeQL Action is checked out locally. Using the default CodeQL Action repository." ); return CODEQL_DEFAULT_ACTION_REPOSITORY; } - return getRequiredEnvParam("GITHUB_ACTION_REPOSITORY"); + return action.env.getRequired("GITHUB_ACTION_REPOSITORY" /* GITHUB_ACTION_REPOSITORY */); } // src/tar.ts @@ -176043,7 +176043,10 @@ var CODEQL_BUNDLE_VERSION_ALIAS = ["linked", "latest"]; var CODEQL_NIGHTLY_TOOLS_INPUTS = ["nightly", "nightly-latest"]; var CODEQL_TOOLCACHE_INPUT = "toolcache"; async function getCodeQLBundleDownloadURL(tagName, apiDetails, codeQLBundleName, logger2) { - const codeQLActionRepository = getCodeQLActionRepository(logger2); + const codeQLActionRepository = getCodeQLActionRepository({ + logger: logger2, + env: getEnv() + }); const potentialDownloadSources = [ // This GitHub instance, and this Action. [apiDetails.url, codeQLActionRepository], diff --git a/src/actions-util.ts b/src/actions-util.ts index 6c7d6a78d2..d51fdbe1da 100644 --- a/src/actions-util.ts +++ b/src/actions-util.ts @@ -103,7 +103,7 @@ export function getWorkflowEventName(env: Env = getEnv()) { * Returns whether the current workflow is executing a local copy of the Action, e.g. we're running * a workflow on the codeql-action repo itself. */ -export function isRunningLocalAction(env: Env = getEnv()): boolean { +export function isRunningLocalAction(env: ReadOnlyEnv = getEnv()): boolean { const relativeScriptPath = getRelativeScriptPath(env); return ( relativeScriptPath.startsWith("..") || path.isAbsolute(relativeScriptPath) @@ -115,7 +115,7 @@ export function isRunningLocalAction(env: Env = getEnv()): boolean { * * This can be used to get the Action's name or tell if we're running a local Action. */ -function getRelativeScriptPath(env: Env): string { +function getRelativeScriptPath(env: ReadOnlyEnv): string { const runnerTemp = env.getRequired(ActionsEnvVars.RUNNER_TEMP); const actionsDirectory = path.join(path.dirname(runnerTemp), "_actions"); return path.relative(actionsDirectory, __filename); diff --git a/src/setup-codeql.ts b/src/setup-codeql.ts index 793191b8b1..c97729471f 100644 --- a/src/setup-codeql.ts +++ b/src/setup-codeql.ts @@ -82,7 +82,10 @@ async function getCodeQLBundleDownloadURL( codeQLBundleName: string, logger: Logger, ): Promise { - const codeQLActionRepository = getCodeQLActionRepository(logger); + const codeQLActionRepository = getCodeQLActionRepository({ + logger, + env: getEnv(), + }); const potentialDownloadSources = [ // This GitHub instance, and this Action. [apiDetails.url, codeQLActionRepository], diff --git a/src/setup/repository.test.ts b/src/setup/repository.test.ts index c3dccbf9c2..56eb74786f 100644 --- a/src/setup/repository.test.ts +++ b/src/setup/repository.test.ts @@ -4,28 +4,31 @@ import test from "ava"; import * as sinon from "sinon"; import * as actionsUtil from "../actions-util"; -import { getRunnerLogger } from "../logging"; -import { setupTests } from "../testing-utils"; +import { ActionsEnvVars } from "../environment"; +import { callee, setupTests } from "../testing-utils"; import { initializeEnvironment } from "../util"; import { getCodeQLActionRepository } from "./repository"; setupTests(test); -test.serial("getCodeQLActionRepository", (t) => { - const logger = getRunnerLogger(true); - +test.serial("getCodeQLActionRepository", async (t) => { initializeEnvironment("1.2.3"); + const target = callee(getCodeQLActionRepository) + .withArgs() + .withEnv((env) => { + env.set(ActionsEnvVars.RUNNER_TEMP, path.dirname(__dirname)); + }); + // isRunningLocalAction() === true - delete process.env["GITHUB_ACTION_REPOSITORY"]; - process.env["RUNNER_TEMP"] = path.dirname(__dirname); - const repoLocalRunner = getCodeQLActionRepository(logger); - t.deepEqual(repoLocalRunner, "github/codeql-action"); + await target.passes(t.deepEqual, "github/codeql-action"); // isRunningLocalAction() === false sinon.stub(actionsUtil, "isRunningLocalAction").returns(false); - process.env["GITHUB_ACTION_REPOSITORY"] = "xxx/yyy"; - const repoEnv = getCodeQLActionRepository(logger); - t.deepEqual(repoEnv, "xxx/yyy"); + await target + .withEnv((env) => { + env.set(ActionsEnvVars.GITHUB_ACTION_REPOSITORY, "xxx/yyy"); + }) + .passes(t.deepEqual, "xxx/yyy"); }); diff --git a/src/setup/repository.ts b/src/setup/repository.ts index 908cc39ee7..c58470630b 100644 --- a/src/setup/repository.ts +++ b/src/setup/repository.ts @@ -1,19 +1,30 @@ +import { ActionState } from "../action-common"; import { isRunningLocalAction } from "../actions-util"; -import { Logger } from "../logging"; -import * as util from "../util"; +import { ActionsEnvVars } from "../environment"; +/** The NWO of the standard CodeQL Action repository. */ export const CODEQL_DEFAULT_ACTION_REPOSITORY = "github/codeql-action"; -export function getCodeQLActionRepository(logger: Logger): string { - if (isRunningLocalAction()) { +/** + * Retrieves the NWO of the repository that the CodeQL Action is loaded from. + * Typically, this will be `github/codeql-action`, but it may be something else for + * forks of the Action or in environments other than GitHub.com. + * + * If this function detects that we are running the Action locally (e.g. in tests), + * then we default to `CODEQL_DEFAULT_ACTION_REPOSITORY`. + */ +export function getCodeQLActionRepository( + action: ActionState<["ReadOnlyEnv", "Logger"]>, +): string { + if (isRunningLocalAction(action.env)) { // This handles the case where the Action does not come from an Action repository, // e.g. our integration tests which use the Action code from the current checkout. // In these cases, the GITHUB_ACTION_REPOSITORY environment variable is not set. - logger.info( + action.logger.info( "The CodeQL Action is checked out locally. Using the default CodeQL Action repository.", ); return CODEQL_DEFAULT_ACTION_REPOSITORY; } - return util.getRequiredEnvParam("GITHUB_ACTION_REPOSITORY"); + return action.env.getRequired(ActionsEnvVars.GITHUB_ACTION_REPOSITORY); } From da07d40a0f387325a497ae40e0e02b45044b7f38 Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Tue, 6 Oct 2026 14:29:42 +0100 Subject: [PATCH 04/25] Refactor `getCodeQLBundleDownloadURL` out of `setup-codeql` --- lib/entry-points.js | 172 ++++++++++++++++------------------------ package-lock.json | 2 +- package.json | 1 - src/setup-codeql.ts | 73 ++--------------- src/setup/repository.ts | 99 +++++++++++++++++++++++ 5 files changed, 176 insertions(+), 171 deletions(-) diff --git a/lib/entry-points.js b/lib/entry-points.js index 3a4bdcb05c..71b6915a9c 100644 --- a/lib/entry-points.js +++ b/lib/entry-points.js @@ -42967,11 +42967,11 @@ var require_tracingPolicy = __commonJS({ var import_log = require_log3(); var import_core_util = require_commonjs4(); var import_restError = require_restError3(); - var import_util58 = require_internal3(); + var import_util59 = require_internal3(); var tracingPolicyName2 = "tracingPolicy"; function tracingPolicy2(options = {}) { const userAgentPromise = (0, import_userAgent.getUserAgentValue)(options.userAgentPrefix); - const sanitizer = new import_util58.Sanitizer({ + const sanitizer = new import_util59.Sanitizer({ additionalAllowedQueryParameters: options.additionalAllowedQueryParameters }); const tracingClient = tryCreateTracingClient(); @@ -46597,11 +46597,11 @@ var require_response2 = __commonJS({ }); module2.exports = __toCommonJS2(response_exports); var import_core_rest_pipeline = require_commonjs6(); - var import_util58 = require_util9(); + var import_util59 = require_util9(); var originalResponse = /* @__PURE__ */ Symbol("Original FullOperationResponse"); function toCompatResponse2(response, options) { - let request3 = (0, import_util58.toWebResourceLike)(response.request); - let headers = (0, import_util58.toHttpHeadersLike)(response.headers); + let request3 = (0, import_util59.toWebResourceLike)(response.request); + let headers = (0, import_util59.toHttpHeadersLike)(response.headers); if (options?.createProxy) { return new Proxy(response, { get(target, prop, receiver) { @@ -46642,7 +46642,7 @@ var require_response2 = __commonJS({ return { ...compatResponse, headers, - request: (0, import_util58.toPipelineRequest)(compatResponse.request) + request: (0, import_util59.toPipelineRequest)(compatResponse.request) }; } } @@ -46749,7 +46749,7 @@ var require_requestPolicyFactoryPolicy = __commonJS({ requestPolicyFactoryPolicyName: () => requestPolicyFactoryPolicyName2 }); module2.exports = __toCommonJS2(requestPolicyFactoryPolicy_exports); - var import_util58 = require_util9(); + var import_util59 = require_util9(); var import_response = require_response2(); var HttpPipelineLogLevel2 = /* @__PURE__ */ ((HttpPipelineLogLevel22) => { HttpPipelineLogLevel22[HttpPipelineLogLevel22["ERROR"] = 1] = "ERROR"; @@ -46773,14 +46773,14 @@ var require_requestPolicyFactoryPolicy = __commonJS({ async sendRequest(request3, next) { let httpPipeline = { async sendRequest(httpRequest) { - const response2 = await next((0, import_util58.toPipelineRequest)(httpRequest)); + const response2 = await next((0, import_util59.toPipelineRequest)(httpRequest)); return (0, import_response.toCompatResponse)(response2, { createProxy: true }); } }; for (const factory of orderedFactories) { httpPipeline = factory.create(httpPipeline, mockRequestPolicyOptions); } - const webResourceLike = (0, import_util58.toWebResourceLike)(request3, { createProxy: true }); + const webResourceLike = (0, import_util59.toWebResourceLike)(request3, { createProxy: true }); const response = await httpPipeline.sendRequest(webResourceLike); return (0, import_response.toPipelineResponse)(response); } @@ -46815,12 +46815,12 @@ var require_httpClientAdapter = __commonJS({ }); module2.exports = __toCommonJS2(httpClientAdapter_exports); var import_response = require_response2(); - var import_util58 = require_util9(); + var import_util59 = require_util9(); function convertHttpClient2(requestPolicyClient) { return { sendRequest: async (request3) => { const response = await requestPolicyClient.sendRequest( - (0, import_util58.toWebResourceLike)(request3, { createProxy: true }) + (0, import_util59.toWebResourceLike)(request3, { createProxy: true }) ); return (0, import_response.toPipelineResponse)(response); } @@ -46858,7 +46858,7 @@ var require_commonjs9 = __commonJS({ disableKeepAlivePolicyName: () => import_disableKeepAlivePolicy.disableKeepAlivePolicyName, requestPolicyFactoryPolicyName: () => import_requestPolicyFactoryPolicy.requestPolicyFactoryPolicyName, toCompatResponse: () => import_response.toCompatResponse, - toHttpHeadersLike: () => import_util58.toHttpHeadersLike + toHttpHeadersLike: () => import_util59.toHttpHeadersLike }); module2.exports = __toCommonJS2(src_exports); var import_extendedClient = require_extendedClient(); @@ -46866,7 +46866,7 @@ var require_commonjs9 = __commonJS({ var import_requestPolicyFactoryPolicy = require_requestPolicyFactoryPolicy(); var import_disableKeepAlivePolicy = require_disableKeepAlivePolicy(); var import_httpClientAdapter = require_httpClientAdapter(); - var import_util58 = require_util9(); + var import_util59 = require_util9(); } }); @@ -107096,41 +107096,6 @@ var require_tool_cache = __commonJS({ } }); -// node_modules/fast-deep-equal/index.js -var require_fast_deep_equal = __commonJS({ - "node_modules/fast-deep-equal/index.js"(exports2, module2) { - "use strict"; - module2.exports = function equal(a, b) { - if (a === b) return true; - if (a && b && typeof a == "object" && typeof b == "object") { - if (a.constructor !== b.constructor) return false; - var length, i, keys; - if (Array.isArray(a)) { - length = a.length; - if (length != b.length) return false; - for (i = length; i-- !== 0; ) - if (!equal(a[i], b[i])) return false; - return true; - } - if (a.constructor === RegExp) return a.source === b.source && a.flags === b.flags; - if (a.valueOf !== Object.prototype.valueOf) return a.valueOf() === b.valueOf(); - if (a.toString !== Object.prototype.toString) return a.toString() === b.toString(); - keys = Object.keys(a); - length = keys.length; - if (length !== Object.keys(b).length) return false; - for (i = length; i-- !== 0; ) - if (!Object.prototype.hasOwnProperty.call(b, keys[i])) return false; - for (i = length; i-- !== 0; ) { - var key = keys[i]; - if (!equal(a[key], b[key])) return false; - } - return true; - } - return a !== a && b !== b; - }; - } -}); - // node_modules/follow-redirects/debug.js var require_debug3 = __commonJS({ "node_modules/follow-redirects/debug.js"(exports2, module2) { @@ -175209,7 +175174,6 @@ var path13 = __toESM(require("path")); var import_perf_hooks4 = require("perf_hooks"); var core12 = __toESM(require_core()); var toolcache3 = __toESM(require_tool_cache()); -var import_fast_deep_equal = __toESM(require_fast_deep_equal()); var semver10 = __toESM(require_semver2()); // src/codeql-bundle.ts @@ -175645,6 +175609,55 @@ function getCodeQLActionRepository(action) { } return action.env.getRequired("GITHUB_ACTION_REPOSITORY" /* GITHUB_ACTION_REPOSITORY */); } +var DEFAULT_DOWNLOAD_SOURCE = [ + GITHUB_DOTCOM_URL, + CODEQL_DEFAULT_ACTION_REPOSITORY +]; +var isSameDownloadSource = ([srcUrl, srcRepo], [otherUrl, otherRepo]) => { + return srcUrl === otherUrl && srcRepo === otherRepo; +}; +async function getCodeQLAssetDownloadURL(action, apiDetails, tagName, assetName, assetKind = "CodeQL bundle") { + const codeQLActionRepository = getCodeQLActionRepository(action); + const potentialDownloadSources = [ + // This GitHub instance, and this Action. + [apiDetails.url, codeQLActionRepository], + // This GitHub instance, and the canonical Action. + [apiDetails.url, CODEQL_DEFAULT_ACTION_REPOSITORY], + // GitHub.com, and the canonical Action. + DEFAULT_DOWNLOAD_SOURCE + ]; + const uniqueDownloadSources = potentialDownloadSources.filter( + (source, index2, self2) => { + return !self2.slice(0, index2).some((other) => isSameDownloadSource(source, other)); + } + ); + for (const [apiURL, repository] of uniqueDownloadSources) { + if (isSameDownloadSource(DEFAULT_DOWNLOAD_SOURCE, [apiURL, repository])) { + break; + } + const [repositoryOwner, repositoryName] = repository.split("/"); + try { + const release2 = await getApiClient().rest.repos.getReleaseByTag({ + owner: repositoryOwner, + repo: repositoryName, + tag: tagName + }); + for (const asset of release2.data.assets) { + if (asset.name === assetName) { + action.logger.info( + `Found ${assetKind} ${assetName} in ${repository} on ${apiURL} with URL ${asset.url}.` + ); + return asset.url; + } + } + } catch (e) { + action.logger.info( + `Looked for ${assetKind} ${assetName} in ${repository} on ${apiURL} but got error ${e}.` + ); + } + } + return `https://github.com/${CODEQL_DEFAULT_ACTION_REPOSITORY}/releases/download/${tagName}/${assetName}`; +} // src/tar.ts var import_child_process = require("child_process"); @@ -176042,52 +176055,6 @@ var CODEQL_NIGHTLIES_REPOSITORY_NAME = "codeql-cli-nightlies"; var CODEQL_BUNDLE_VERSION_ALIAS = ["linked", "latest"]; var CODEQL_NIGHTLY_TOOLS_INPUTS = ["nightly", "nightly-latest"]; var CODEQL_TOOLCACHE_INPUT = "toolcache"; -async function getCodeQLBundleDownloadURL(tagName, apiDetails, codeQLBundleName, logger2) { - const codeQLActionRepository = getCodeQLActionRepository({ - logger: logger2, - env: getEnv() - }); - const potentialDownloadSources = [ - // This GitHub instance, and this Action. - [apiDetails.url, codeQLActionRepository], - // This GitHub instance, and the canonical Action. - [apiDetails.url, CODEQL_DEFAULT_ACTION_REPOSITORY], - // GitHub.com, and the canonical Action. - [GITHUB_DOTCOM_URL, CODEQL_DEFAULT_ACTION_REPOSITORY] - ]; - const uniqueDownloadSources = potentialDownloadSources.filter( - (source, index2, self2) => { - return !self2.slice(0, index2).some((other) => (0, import_fast_deep_equal.default)(source, other)); - } - ); - for (const downloadSource of uniqueDownloadSources) { - const [apiURL, repository] = downloadSource; - if (apiURL === GITHUB_DOTCOM_URL && repository === CODEQL_DEFAULT_ACTION_REPOSITORY) { - break; - } - const [repositoryOwner, repositoryName] = repository.split("/"); - try { - const release2 = await getApiClient().rest.repos.getReleaseByTag({ - owner: repositoryOwner, - repo: repositoryName, - tag: tagName - }); - for (const asset of release2.data.assets) { - if (asset.name === codeQLBundleName) { - logger2.info( - `Found CodeQL bundle ${codeQLBundleName} in ${repository} on ${apiURL} with URL ${asset.url}.` - ); - return asset.url; - } - } - } catch (e) { - logger2.info( - `Looked for CodeQL bundle ${codeQLBundleName} in ${repository} on ${apiURL} but got error ${e}.` - ); - } - } - return `https://github.com/${CODEQL_DEFAULT_ACTION_REPOSITORY}/releases/download/${tagName}/${codeQLBundleName}`; -} function tryGetBundleVersionFromTagName(tagName, logger2) { const match2 = tagName.match(/^codeql-bundle-(.+)$/); if (match2 === null || match2.length < 2) { @@ -176444,9 +176411,10 @@ async function getCodeQLSource(toolsInput, defaultCliVersion, rawLanguages, useO ); } compressionMethod = cliVersion2 !== void 0 && await useZstdBundle(cliVersion2, tarSupportsZstd) ? "zstd" : "gzip"; + const action = { env: getEnv(), logger: logger2 }; const platform2 = getBundlePlatform(); const perLanguageBundleLanguage = await getPerLanguageBundleLanguage( - { env: getEnv(), features, logger: logger2 }, + { ...action, features }, { rawLanguages, cliVersion: cliVersion2, @@ -176455,11 +176423,11 @@ async function getCodeQLSource(toolsInput, defaultCliVersion, rawLanguages, useO variant } ); - const resolveBundleURL = (language) => getCodeQLBundleDownloadURL( - bundleTagName, + const resolveBundleURL = (language) => getCodeQLAssetDownloadURL( + action, apiDetails, - getCodeQLBundleName(compressionMethod, platform2, language), - logger2 + bundleTagName, + getCodeQLBundleName(compressionMethod, platform2, language) ); const combinedBundleURL = await resolveBundleURL(); if (perLanguageBundleLanguage !== void 0) { @@ -182926,7 +182894,7 @@ var import_async = __toESM(require_async(), 1); var import_path7 = require("path"); // node_modules/archiver/lib/error.js -var import_util36 = __toESM(require("util"), 1); +var import_util37 = __toESM(require("util"), 1); var ERROR_CODES = { ABORTED: "archive was aborted", DIRECTORYDIRPATHREQUIRED: "diretory dirpath argument must be a non-empty string value", @@ -182951,7 +182919,7 @@ function ArchiverError(code, data) { this.code = code; this.data = data; } -import_util36.default.inherits(ArchiverError, Error); +import_util37.default.inherits(ArchiverError, Error); // node_modules/archiver/lib/core.js var import_readable_stream2 = __toESM(require_ours(), 1); diff --git a/package-lock.json b/package-lock.json index 27bf393bfd..8d3781fe5b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -27,7 +27,6 @@ "@octokit/plugin-rest-endpoint-methods": "^18.0.0", "@octokit/plugin-retry": "^8.1.1", "archiver": "^8.0.0", - "fast-deep-equal": "^3.1.3", "follow-redirects": "^1.16.0", "get-folder-size": "^5.0.0", "https-proxy-agent": "^7.0.6", @@ -5817,6 +5816,7 @@ }, "node_modules/fast-deep-equal": { "version": "3.1.3", + "dev": true, "license": "MIT" }, "node_modules/fast-diff": { diff --git a/package.json b/package.json index 5ebf14292b..52c11027f8 100644 --- a/package.json +++ b/package.json @@ -35,7 +35,6 @@ "@octokit/plugin-rest-endpoint-methods": "^18.0.0", "@octokit/plugin-retry": "^8.1.1", "archiver": "^8.0.0", - "fast-deep-equal": "^3.1.3", "follow-redirects": "^1.16.0", "get-folder-size": "^5.0.0", "https-proxy-agent": "^7.0.6", diff --git a/src/setup-codeql.ts b/src/setup-codeql.ts index c97729471f..29a1809e4b 100644 --- a/src/setup-codeql.ts +++ b/src/setup-codeql.ts @@ -5,7 +5,6 @@ import { performance } from "perf_hooks"; import * as core from "@actions/core"; import * as toolcache from "@actions/tool-cache"; -import { default as deepEqual } from "fast-deep-equal"; import * as semver from "semver"; import { v4 as uuidV4 } from "uuid"; @@ -44,10 +43,7 @@ import { logPerLanguageBundleFallback, } from "./per-language-bundles"; import { getBundlePlatform } from "./platform"; -import { - CODEQL_DEFAULT_ACTION_REPOSITORY, - getCodeQLActionRepository, -} from "./setup/repository"; +import { getCodeQLAssetDownloadURL } from "./setup/repository"; import * as tar from "./tar"; import { deleteToolcacheBundles, @@ -76,64 +72,6 @@ const CODEQL_BUNDLE_VERSION_ALIAS: string[] = ["linked", "latest"]; const CODEQL_NIGHTLY_TOOLS_INPUTS = ["nightly", "nightly-latest"]; const CODEQL_TOOLCACHE_INPUT = "toolcache"; -async function getCodeQLBundleDownloadURL( - tagName: string, - apiDetails: api.GitHubApiDetails, - codeQLBundleName: string, - logger: Logger, -): Promise { - const codeQLActionRepository = getCodeQLActionRepository({ - logger, - env: getEnv(), - }); - const potentialDownloadSources = [ - // This GitHub instance, and this Action. - [apiDetails.url, codeQLActionRepository], - // This GitHub instance, and the canonical Action. - [apiDetails.url, CODEQL_DEFAULT_ACTION_REPOSITORY], - // GitHub.com, and the canonical Action. - [util.GITHUB_DOTCOM_URL, CODEQL_DEFAULT_ACTION_REPOSITORY], - ]; - // We now filter out any duplicates. - // Duplicates will happen either because the GitHub instance is GitHub.com, or because the Action is not a fork. - const uniqueDownloadSources = potentialDownloadSources.filter( - (source, index, self) => { - return !self.slice(0, index).some((other) => deepEqual(source, other)); - }, - ); - for (const downloadSource of uniqueDownloadSources) { - const [apiURL, repository] = downloadSource; - // If we've reached the final case, short-circuit the API check since we know the bundle exists and is public. - if ( - apiURL === util.GITHUB_DOTCOM_URL && - repository === CODEQL_DEFAULT_ACTION_REPOSITORY - ) { - break; - } - const [repositoryOwner, repositoryName] = repository.split("/"); - try { - const release = await api.getApiClient().rest.repos.getReleaseByTag({ - owner: repositoryOwner, - repo: repositoryName, - tag: tagName, - }); - for (const asset of release.data.assets) { - if (asset.name === codeQLBundleName) { - logger.info( - `Found CodeQL bundle ${codeQLBundleName} in ${repository} on ${apiURL} with URL ${asset.url}.`, - ); - return asset.url; - } - } - } catch (e) { - logger.info( - `Looked for CodeQL bundle ${codeQLBundleName} in ${repository} on ${apiURL} but got error ${e}.`, - ); - } - } - return `https://github.com/${CODEQL_DEFAULT_ACTION_REPOSITORY}/releases/download/${tagName}/${codeQLBundleName}`; -} - function tryGetBundleVersionFromTagName( tagName: string, logger: Logger, @@ -707,9 +645,10 @@ export async function getCodeQLSource( ? "zstd" : "gzip"; + const action = { env: getEnv(), logger }; const platform = getBundlePlatform(); const perLanguageBundleLanguage = await getPerLanguageBundleLanguage( - { env: getEnv(), features, logger }, + { ...action, features }, { rawLanguages, cliVersion, @@ -721,11 +660,11 @@ export async function getCodeQLSource( // Resolves the combined or per-language bundle URL for the requested release. const resolveBundleURL = (language?: BuiltInLanguage) => - getCodeQLBundleDownloadURL( - bundleTagName, + getCodeQLAssetDownloadURL( + action, apiDetails, + bundleTagName, getCodeQLBundleName(compressionMethod, platform, language), - logger, ); const combinedBundleURL = await resolveBundleURL(); diff --git a/src/setup/repository.ts b/src/setup/repository.ts index c58470630b..53ca44f3e2 100644 --- a/src/setup/repository.ts +++ b/src/setup/repository.ts @@ -1,6 +1,8 @@ import { ActionState } from "../action-common"; import { isRunningLocalAction } from "../actions-util"; +import * as api from "../api-client"; import { ActionsEnvVars } from "../environment"; +import { GITHUB_DOTCOM_URL } from "../util"; /** The NWO of the standard CodeQL Action repository. */ export const CODEQL_DEFAULT_ACTION_REPOSITORY = "github/codeql-action"; @@ -28,3 +30,100 @@ export function getCodeQLActionRepository( return action.env.getRequired(ActionsEnvVars.GITHUB_ACTION_REPOSITORY); } + +/**A download source is a pair of strings. */ +export type DownloadSource = [string, string]; + +/** GitHub.com, and the canonical Action. */ +export const DEFAULT_DOWNLOAD_SOURCE: DownloadSource = [ + GITHUB_DOTCOM_URL, + CODEQL_DEFAULT_ACTION_REPOSITORY, +]; + +/** Decides if the two provided download sources are the same. */ +const isSameDownloadSource = ( + [srcUrl, srcRepo]: DownloadSource, + [otherUrl, otherRepo]: DownloadSource, +) => { + return srcUrl === otherUrl && srcRepo === otherRepo; +}; + +/** A download URL is represented as a string. */ +export type DownloadURL = string; + +/** + * Tries to find a download URL for `assetName` in a release tagged with `tagName`. + * + * Depending on where and how the CodeQL Action is running, we may be using different `apiDetails` and there may be + * different options for where to source CodeQL releases from. + * + * This function either returns the download URL for the asset for the first release we find, or + * defaults to the assumed download URL for the asset on the default CodeQL Action repository on GitHub.com. + * In the latter case, this function does not guarantee that the asset actually exists. + * + * @param action The Action state. + * @param apiDetails The details of the GitHub API in use. + * @param tagName The name of the release tag we want to obtain the asset from. + * @param assetName The name of the asset we should look for in the release. + * @param [assetKind="CodeQL bundle"] The kind of asset we are looking for to show in log messages. + * @returns A URL that we can use to download the asset. + */ +export async function getCodeQLAssetDownloadURL( + action: ActionState<["ReadOnlyEnv", "Logger"]>, + apiDetails: api.GitHubApiDetails, + tagName: string, + assetName: string, + assetKind: "CodeQL bundle" = "CodeQL bundle", +): Promise { + const codeQLActionRepository = getCodeQLActionRepository(action); + + const potentialDownloadSources: Array<[string, string]> = [ + // This GitHub instance, and this Action. + [apiDetails.url, codeQLActionRepository], + // This GitHub instance, and the canonical Action. + [apiDetails.url, CODEQL_DEFAULT_ACTION_REPOSITORY], + // GitHub.com, and the canonical Action. + DEFAULT_DOWNLOAD_SOURCE, + ]; + + // We now filter out any duplicates. + // Duplicates will happen either because the GitHub instance is GitHub.com, or because the Action is not a fork. + const uniqueDownloadSources = potentialDownloadSources.filter( + (source, index, self) => { + return !self + .slice(0, index) + .some((other) => isSameDownloadSource(source, other)); + }, + ); + + for (const [apiURL, repository] of uniqueDownloadSources) { + // If we've reached the final case, short-circuit the API check since we know the bundle exists and is public. + if (isSameDownloadSource(DEFAULT_DOWNLOAD_SOURCE, [apiURL, repository])) { + break; + } + + const [repositoryOwner, repositoryName] = repository.split("/"); + try { + const release = await api.getApiClient().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 ${apiURL} with URL ${asset.url}.`, + ); + return asset.url; + } + } + } catch (e) { + action.logger.info( + `Looked for ${assetKind} ${assetName} in ${repository} on ${apiURL} but got error ${e}.`, + ); + } + } + + return `https://github.com/${CODEQL_DEFAULT_ACTION_REPOSITORY}/releases/download/${tagName}/${assetName}`; +} From 35f12ded428e57cae1cfa0e0c84b789eb44a4fda Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Tue, 6 Oct 2026 16:10:21 +0100 Subject: [PATCH 05/25] Add utility for assertions on sinon stubs with better output by default --- src/testing-utils.ts | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/src/testing-utils.ts b/src/testing-utils.ts index df81f51bb4..2b188857a9 100644 --- a/src/testing-utils.ts +++ b/src/testing-utils.ts @@ -163,6 +163,34 @@ export function setupTests(testFn: TestFn) { }); } +export interface AvaSinonAssertions { + /** + * Asserts that `stub` must have been called once with at least the `expected` arguments. + */ + onceWith: ( + stub: sinon.SinonStub, + ...expected: sinon.MatchPartialArguments + ) => boolean; +} + +/** Adds additional assertions for use with `sinon` to `t`. */ +export function addSinonAssertions( + t: ExecutionContext, +): ExecutionContext & AvaSinonAssertions { + return { + ...t, + onceWith: (stub, ...expected) => { + if (stub.callCount > 0) { + // Reduce the arguments of the first call to the same number that was provided, + // so that extra arguments don't lead to a failure. + return t.deepEqual(stub.args[0].slice(0, expected.length), expected); + } else { + return t.fail("The stub wasn't called."); + } + }, + }; +} + /** * Declare a reusable test implementation, with better type safety than `test.macro`. */ From a4f9f6fc121605c2b77d8dd9d562eb148dba7c13 Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Tue, 6 Oct 2026 16:11:27 +0100 Subject: [PATCH 06/25] Type the `assetKind` parameter --- src/setup/repository.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/setup/repository.ts b/src/setup/repository.ts index 53ca44f3e2..4101be6770 100644 --- a/src/setup/repository.ts +++ b/src/setup/repository.ts @@ -51,6 +51,9 @@ const isSameDownloadSource = ( /** A download URL is represented as a string. */ export type DownloadURL = string; +/** Enumerates log-friendly descriptions of different kinds of assets we might look for in releases. */ +export type ReleaseAssetKind = "CodeQL bundle" | "private registry proxy"; + /** * Tries to find a download URL for `assetName` in a release tagged with `tagName`. * @@ -73,7 +76,7 @@ export async function getCodeQLAssetDownloadURL( apiDetails: api.GitHubApiDetails, tagName: string, assetName: string, - assetKind: "CodeQL bundle" = "CodeQL bundle", + assetKind: ReleaseAssetKind = "CodeQL bundle", ): Promise { const codeQLActionRepository = getCodeQLActionRepository(action); From de99f0ee4b789bac85c60c23cf5617af3a93e08f Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Tue, 6 Oct 2026 16:30:20 +0100 Subject: [PATCH 07/25] Add `getDefaultDotComDownloadURL` --- lib/entry-points.js | 5 ++++- src/setup/repository.ts | 10 +++++++++- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/lib/entry-points.js b/lib/entry-points.js index 71b6915a9c..49d38c393f 100644 --- a/lib/entry-points.js +++ b/lib/entry-points.js @@ -175616,6 +175616,9 @@ var DEFAULT_DOWNLOAD_SOURCE = [ var isSameDownloadSource = ([srcUrl, srcRepo], [otherUrl, otherRepo]) => { return srcUrl === otherUrl && srcRepo === otherRepo; }; +function getDefaultDotComDownloadURL(tagName, assetName) { + return `https://github.com/${CODEQL_DEFAULT_ACTION_REPOSITORY}/releases/download/${tagName}/${assetName}`; +} async function getCodeQLAssetDownloadURL(action, apiDetails, tagName, assetName, assetKind = "CodeQL bundle") { const codeQLActionRepository = getCodeQLActionRepository(action); const potentialDownloadSources = [ @@ -175656,7 +175659,7 @@ async function getCodeQLAssetDownloadURL(action, apiDetails, tagName, assetName, ); } } - return `https://github.com/${CODEQL_DEFAULT_ACTION_REPOSITORY}/releases/download/${tagName}/${assetName}`; + return getDefaultDotComDownloadURL(tagName, assetName); } // src/tar.ts diff --git a/src/setup/repository.ts b/src/setup/repository.ts index 4101be6770..3d760858e7 100644 --- a/src/setup/repository.ts +++ b/src/setup/repository.ts @@ -51,6 +51,14 @@ const isSameDownloadSource = ( /** A download URL is represented as a string. */ export type DownloadURL = string; +/** Gets the default GitHub.com download URL for `assetName` in a release tagged `tagName`. */ +export function getDefaultDotComDownloadURL( + tagName: string, + assetName: string, +): DownloadURL { + return `https://github.com/${CODEQL_DEFAULT_ACTION_REPOSITORY}/releases/download/${tagName}/${assetName}`; +} + /** Enumerates log-friendly descriptions of different kinds of assets we might look for in releases. */ export type ReleaseAssetKind = "CodeQL bundle" | "private registry proxy"; @@ -128,5 +136,5 @@ export async function getCodeQLAssetDownloadURL( } } - return `https://github.com/${CODEQL_DEFAULT_ACTION_REPOSITORY}/releases/download/${tagName}/${assetName}`; + return getDefaultDotComDownloadURL(tagName, assetName); } From 133ffebe62eccf0a18bf2fa6f884063a7fd6ff7d Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Tue, 6 Oct 2026 16:13:32 +0100 Subject: [PATCH 08/25] Use `getCodeQLAssetDownloadURL` for `start-proxy` --- lib/entry-points.js | 62 +++++--------- src/start-proxy-action.ts | 7 +- src/start-proxy.test.ts | 173 +++++++++++++++++++++++++------------- src/start-proxy.ts | 82 +++++------------- 4 files changed, 166 insertions(+), 158 deletions(-) diff --git a/lib/entry-points.js b/lib/entry-points.js index 49d38c393f..84c57c813b 100644 --- a/lib/entry-points.js +++ b/lib/entry-points.js @@ -187489,8 +187489,6 @@ async function sendFailedStatusReport(logger2, startedAt, language, unwrappedErr } } var UPDATEJOB_PROXY = "update-job-proxy"; -var UPDATEJOB_PROXY_VERSION = "v2.0.20250624110901"; -var UPDATEJOB_PROXY_URL_PREFIX = "https://github.com/github/codeql-action/releases/download/codeql-bundle-v2.22.0/"; function isPAT(value) { return isAuthToken(value, [ GITHUB_PAT_CLASSIC_PATTERN, @@ -187608,21 +187606,11 @@ function getCredentials(logger2, registrySecrets, registriesCredentials, languag function getProxyPackage(platform2) { return `${UPDATEJOB_PROXY}-${platform2}.tar.gz`; } -function getFallbackUrl(proxyPackage) { - return `${UPDATEJOB_PROXY_URL_PREFIX}${proxyPackage}`; -} -async function getReleaseByVersion(version) { - return getApiClient().rest.repos.getReleaseByTag({ - owner: "github", - repo: "codeql-action", - tag: version - }); -} async function getCliVersionFromFeatures(features) { const gitHubVersion = await getGitHubVersion(); return await features.getEnabledDefaultCliVersions(gitHubVersion.type); } -async function getDownloadUrl(action) { +async function getDownloadUrl(action, apiDetails) { let platform2 = getBundlePlatform(action.platform, action.arch); if (platform2 === void 0) { action.logger.warning( @@ -187639,33 +187627,25 @@ async function getDownloadUrl(action) { cliVersion, tagName: bundleVersion }; - const cliRelease = await getReleaseByVersion(versionInfo.tagName); - for (const asset of cliRelease.data.assets) { - if (asset.name === proxyPackage) { - action.logger.info( - `Found '${proxyPackage}' in release '${versionInfo.tagName}' at '${asset.url}'` - ); - return { - url: asset.url, - // The `update-job-proxy` doesn't have a version as such. Since we now bundle it - // with CodeQL CLI bundle releases, we use the corresponding CLI version to - // differentiate between (potentially) different versions of `update-job-proxy`. - version: versionInfo.cliVersion - }; - } - } + const proxyAssetUrl = await getCodeQLAssetDownloadURL( + action, + apiDetails, + versionInfo.tagName, + proxyPackage, + "private registry proxy" + ); + return { + url: proxyAssetUrl, + // The `update-job-proxy` doesn't have a version as such. Since we now bundle it + // with CodeQL CLI bundle releases, we use the corresponding CLI version to + // differentiate between (potentially) different versions of `update-job-proxy`. + version: versionInfo.cliVersion + }; } catch (ex) { - action.logger.warning( + throw new Error( `Failed to retrieve information about the linked release: ${getErrorMessage(ex)}` ); } - action.logger.info( - `Did not find '${proxyPackage}' in the linked release, falling back to hard-coded version.` - ); - return { - url: getFallbackUrl(proxyPackage), - version: UPDATEJOB_PROXY_VERSION - }; } async function downloadProxy(logger2, url2, authorization) { try { @@ -187708,13 +187688,12 @@ async function cacheProxy(logger2, source, filename, version) { function getProxyFilename() { return process.platform === "win32" ? `${UPDATEJOB_PROXY}.exe` : UPDATEJOB_PROXY; } -async function getProxyBinaryPath(action) { +async function getProxyBinaryPath(action, apiDetails) { const logger2 = action.logger; const proxyFileName = getProxyFilename(); - const proxyInfo = await getDownloadUrl(action); + const proxyInfo = await getDownloadUrl(action, apiDetails); let proxyBin = toolcache4.find(proxyFileName, proxyInfo.version); if (!proxyBin) { - const apiDetails = getApiDetails(); const authorization = getAuthorizationHeaderFor( logger2, apiDetails, @@ -188091,7 +188070,10 @@ async function run7(action) { all_credentials: credentials, ca }; - const proxyBin = await getProxyBinaryPath({ ...action, features }); + const proxyBin = await getProxyBinaryPath( + { ...action, features }, + getApiDetails(action.env) + ); const proxyInfo = await startProxy( proxyBin, proxyConfig, diff --git a/src/start-proxy-action.ts b/src/start-proxy-action.ts index d965a2dd6b..b9c7795ef1 100644 --- a/src/start-proxy-action.ts +++ b/src/start-proxy-action.ts @@ -5,7 +5,7 @@ import * as core from "@actions/core"; import { Action, ActionState, runInActions } from "./action-common"; import * as actionsUtil from "./actions-util"; -import { getGitHubVersion } from "./api-client"; +import { getApiDetails, getGitHubVersion } from "./api-client"; import { FeatureEnablement, initFeatures } from "./feature-flags"; import { BuiltInLanguage, parseBuiltInLanguage } from "./languages"; import { Logger } from "./logging"; @@ -98,7 +98,10 @@ async function run(action: ActionState<["Base", "Logger", "Env", "Actions"]>) { }; // Start the Proxy - const proxyBin = await getProxyBinaryPath({ ...action, features }); + const proxyBin = await getProxyBinaryPath( + { ...action, features }, + getApiDetails(action.env), + ); const proxyInfo = await startProxy( proxyBin, proxyConfig, diff --git a/src/start-proxy.test.ts b/src/start-proxy.test.ts index bd24897136..b0bdd7bd33 100644 --- a/src/start-proxy.test.ts +++ b/src/start-proxy.test.ts @@ -6,17 +6,21 @@ import test, { ExecutionContext } from "ava"; import sinon from "sinon"; 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 { setUpFeatureFlagTests } from "./feature-flags/testing-util"; import { UnvalidatedObject, validateSchema } from "./json"; import { makeFromSchema } from "./json/testing-util"; import { BuiltInLanguage } from "./languages"; import { getRunnerLogger, Logger } from "./logging"; import { BundlePlatform, getBundlePlatform } from "./platform"; +import * as repository from "./setup/repository"; import * as startProxyExports from "./start-proxy"; import * as statusReport from "./status-report"; import { + addSinonAssertions, assertNotLogged, checkExpectedLogMessages, createFeatures, @@ -24,6 +28,7 @@ import { makeMacro, makeTestToken, RecordingLogger, + SAMPLE_DOTCOM_API_DETAILS, setupTests, withRecordingLoggerAsync, } from "./testing-utils"; @@ -705,15 +710,23 @@ function mockGetApiClient(endpoints: any) { type ReleaseAssets = Array<{ name: string; url?: string }>; -function mockGetReleaseByTag(assets?: ReleaseAssets) { +function mockGetReleaseByTag(expectedTag?: string, assets?: ReleaseAssets) { const getReleaseByTag = assets === undefined ? sinon.stub().rejects() - : sinon.stub().resolves({ - status: 200, - data: { assets }, - headers: {}, - url: "GET /repos/:owner/:repo/releases/tags/:tag", + : sinon.stub().callsFake(({ tag }) => { + if (tag === expectedTag) { + return { + status: 200, + data: { assets }, + headers: {}, + url: "GET /repos/:owner/:repo/releases/tags/:tag", + }; + } else { + return { + status: 404, + }; + } }); return mockGetApiClient({ repos: { getReleaseByTag } }); @@ -751,25 +764,33 @@ test.serial( logger, features, }); - const info = await startProxyExports.getDownloadUrl(state); - t.is(info.version, startProxyExports.UPDATEJOB_PROXY_VERSION); - t.is( - info.url, - startProxyExports.getFallbackUrl( - startProxyExports.getProxyPackage(BundlePlatform.Linux64), - ), + // Stub this to always return `true` so we get the default repo for the tests. + sinon.stub(actionsUtil, "isRunningLocalAction").returns(true); + + const info = await startProxyExports.getDownloadUrl( + state, + SAMPLE_DOTCOM_API_DETAILS, ); t.true( logger.hasMessage(`Unsupported platform android on architecture ppc`), ); + + t.is(info.version, defaults.cliVersion); + t.is( + info.url, + repository.getDefaultDotComDownloadURL( + defaults.bundleVersion, + startProxyExports.getProxyPackage(BundlePlatform.Linux64), + ), + ); }); }, ); test.serial( - "getDownloadUrl returns fallback when `getReleaseByVersion` rejects", + "getDownloadUrl returns default bundle URL for linked version", async (t) => { const logger = new RecordingLogger(); mockGetReleaseByTag(); @@ -777,12 +798,20 @@ test.serial( await withTmpDir(async (tempDir) => { const features = mockOfflineFeatures(tempDir, logger); const state = initAllState({ logger, features }); - const info = await startProxyExports.getDownloadUrl(state); - t.is(info.version, startProxyExports.UPDATEJOB_PROXY_VERSION); + // Stub this to always return `true` so we get the default repo for the tests. + sinon.stub(actionsUtil, "isRunningLocalAction").returns(true); + + const info = await startProxyExports.getDownloadUrl( + state, + SAMPLE_DOTCOM_API_DETAILS, + ); + + t.is(info.version, defaults.cliVersion); t.is( info.url, - startProxyExports.getFallbackUrl( + repository.getDefaultDotComDownloadURL( + defaults.bundleVersion, startProxyExports.getProxyPackage(getTestPlatform(state)), ), ); @@ -791,7 +820,7 @@ test.serial( ); test.serial( - "getDownloadUrl returns fallback when there's no matching release asset", + "getDownloadUrl doesn't check that the default asset exists", async (t) => { const logger = new RecordingLogger(); const testAssets = [[], [{ name: "foo" }]]; @@ -800,14 +829,21 @@ test.serial( const features = mockOfflineFeatures(tempDir, logger); const state = initAllState({ logger, features }); + // Stub this to always return `true` so we get the default repo for the tests. + sinon.stub(actionsUtil, "isRunningLocalAction").returns(true); + for (const assets of testAssets) { - const stub = mockGetReleaseByTag(assets); - const info = await startProxyExports.getDownloadUrl(state); + const stub = mockGetReleaseByTag(defaults.bundleVersion, assets); + const info = await startProxyExports.getDownloadUrl( + state, + SAMPLE_DOTCOM_API_DETAILS, + ); - t.is(info.version, startProxyExports.UPDATEJOB_PROXY_VERSION); + t.is(info.version, defaults.cliVersion); t.is( info.url, - startProxyExports.getFallbackUrl( + repository.getDefaultDotComDownloadURL( + defaults.bundleVersion, startProxyExports.getProxyPackage(getTestPlatform(state)), ), ); @@ -828,11 +864,22 @@ test.serial("getDownloadUrl returns matching release asset", async (t) => { url: "url-we-want", }, ]; - mockGetReleaseByTag(assets); + const getReleaseByTag = mockGetReleaseByTag(defaults.bundleVersion, assets); await withTmpDir(async (tempDir) => { + state.env.set(ActionsEnvVars.RUNNER_TEMP, tempDir); const features = mockOfflineFeatures(tempDir, logger); - const info = await startProxyExports.getDownloadUrl({ ...state, features }); + + // Stub this to always return `false` so we force `getCodeQLAssetDownloadURL` to use the (mocked) API. + sinon.stub(actionsUtil, "isRunningLocalAction").returns(false); + state.env.set(ActionsEnvVars.GITHUB_ACTION_REPOSITORY, "test/repo"); + + const info = await startProxyExports.getDownloadUrl( + { ...state, features }, + SAMPLE_DOTCOM_API_DETAILS, + ); + + t.is(getReleaseByTag.callCount, 1); t.is(info.version, defaults.cliVersion); t.is(info.url, "url-we-want"); @@ -965,9 +1012,13 @@ test.serial( const toolcachePath = "/path/to/proxy/dir"; sinon.stub(toolcache, "find").returns(toolcachePath); + // Stub this to always return `true` so we get the default repo for the tests. + sinon.stub(actionsUtil, "isRunningLocalAction").returns(true); + const features = mockOfflineFeatures(tempDir, logger); const path = await startProxyExports.getProxyBinaryPath( initAllState({ logger, features }), + SAMPLE_DOTCOM_API_DETAILS, ); t.assert(path); @@ -985,7 +1036,7 @@ test.serial( const logger = new RecordingLogger(); const state = initAllState({ logger }); const downloadUrl = "url-we-want"; - mockGetReleaseByTag([ + const getApiClient = mockGetReleaseByTag(defaults.bundleVersion, [ { name: startProxyExports.getProxyPackage(getTestPlatform(state)), url: downloadUrl, @@ -994,11 +1045,6 @@ test.serial( const toolcachePath = "/path/to/proxy/dir"; const find = sinon.stub(toolcache, "find").returns(""); - const getApiDetails = sinon.stub(apiClient, "getApiDetails").returns({ - auth: "", - url: "", - apiURL: "", - }); const getAuthorizationHeaderFor = sinon .stub(apiClient, "getAuthorizationHeaderFor") .returns(undefined); @@ -1012,15 +1058,27 @@ test.serial( .resolves(extractedPath); const cacheDir = sinon.stub(toolcache, "cacheDir").resolves(toolcachePath); - const path = await startProxyExports.getProxyBinaryPath({ - ...state, - features: createFeatures([]), - }); + // Stub this to always return `false` so we force `getCodeQLAssetDownloadURL` to use the (mocked) API. + sinon.stub(actionsUtil, "isRunningLocalAction").returns(false); + state.env.set(ActionsEnvVars.GITHUB_ACTION_REPOSITORY, "test/repo"); + const path = await startProxyExports.getProxyBinaryPath( + { + ...state, + features: createFeatures([]), + }, + SAMPLE_DOTCOM_API_DETAILS, + ); + + t.is(getApiClient.callCount, 1); t.assert(find.calledOnce); - t.assert(getApiDetails.calledOnce); t.assert(getAuthorizationHeaderFor.calledOnce); - t.assert(downloadTool.calledOnceWith(downloadUrl)); + + const st = addSinonAssertions(t); + st.onceWith(downloadTool, downloadUrl); + st.onceWith(extractTar, archivePath); + st.onceWith(cacheDir, extractedPath); + t.assert(extractTar.calledOnceWith(archivePath)); t.assert(cacheDir.calledOnceWith(extractedPath)); t.assert(path); @@ -1030,7 +1088,7 @@ test.serial( ); checkExpectedLogMessages(t, logger.messages, [ - `Found '${startProxyExports.getProxyPackage(getTestPlatform(state))}' in release '${defaults.bundleVersion}' at '${downloadUrl}'`, + `Found private registry proxy ${startProxyExports.getProxyPackage(getTestPlatform(state))} in test/repo on https://github.com with URL ${downloadUrl}`, ]); }, ); @@ -1042,8 +1100,8 @@ test.serial( const state = initAllState({ logger }); const expectedTag = "codeql-bundle-v2.20.1"; const expectedParams = { - owner: "github", - repo: "codeql-action", + owner: "test", + repo: "repo", tag: expectedTag, }; const downloadUrl = "url-we-want"; @@ -1066,11 +1124,6 @@ test.serial( await withTmpDir(async (tempDir) => { const toolcachePath = "/path/to/proxy/dir"; const find = sinon.stub(toolcache, "find").returns(""); - const getApiDetails = sinon.stub(apiClient, "getApiDetails").returns({ - auth: "", - url: "", - apiURL: "", - }); const getAuthorizationHeaderFor = sinon .stub(apiClient, "getAuthorizationHeaderFor") .returns(undefined); @@ -1100,22 +1153,28 @@ test.serial( .resolves({ enabledVersions: [{ cliVersion: "2.20.1", tagName: expectedTag }], }); - const path = await startProxyExports.getProxyBinaryPath({ - ...state, - features, - }); - t.assert(getDefaultCliVersion.calledOnce); - sinon.assert.calledOnceWithMatch( - getReleaseByTag, - sinon.match(expectedParams), + // Stub this to always return `false` so we force `getCodeQLAssetDownloadURL` to use the (mocked) API. + sinon.stub(actionsUtil, "isRunningLocalAction").returns(false); + state.env.set(ActionsEnvVars.GITHUB_ACTION_REPOSITORY, "test/repo"); + + const path = await startProxyExports.getProxyBinaryPath( + { + ...state, + features, + }, + SAMPLE_DOTCOM_API_DETAILS, ); + + t.assert(getDefaultCliVersion.calledOnce); + + const st = addSinonAssertions(t); + st.onceWith(getReleaseByTag, expectedParams); t.assert(find.calledOnce); - t.assert(getApiDetails.calledOnce); t.assert(getAuthorizationHeaderFor.calledOnce); - t.assert(downloadTool.calledOnceWith(downloadUrl)); - t.assert(extractTar.calledOnceWith(archivePath)); - t.assert(cacheDir.calledOnceWith(extractedPath)); + st.onceWith(downloadTool, downloadUrl); + st.onceWith(extractTar, archivePath); + st.onceWith(cacheDir, extractedPath); t.assert(path); t.is( @@ -1125,7 +1184,7 @@ test.serial( }); checkExpectedLogMessages(t, logger.messages, [ - `Found '${startProxyExports.getProxyPackage(getTestPlatform(state))}' in release '${expectedTag}' at '${downloadUrl}'`, + `Found private registry proxy ${startProxyExports.getProxyPackage(getTestPlatform(state))} in test/repo on https://github.com with URL url-we-want.`, ]); }, ); diff --git a/src/start-proxy.ts b/src/start-proxy.ts index 6221c9940f..9558b119ee 100644 --- a/src/start-proxy.ts +++ b/src/start-proxy.ts @@ -5,10 +5,9 @@ import * as toolcache from "@actions/tool-cache"; import { ActionState } from "./action-common"; import { - getApiClient, - getApiDetails, getAuthorizationHeaderFor, getGitHubVersion, + GitHubApiDetails, } from "./api-client"; import * as artifactScanner from "./artifact-scanner"; import { Config } from "./config-utils"; @@ -22,6 +21,7 @@ import * as json from "./json"; import { BuiltInLanguage } from "./languages"; import { Logger } from "./logging"; import { BundlePlatform, getBundlePlatform } from "./platform"; +import { getCodeQLAssetDownloadURL } from "./setup/repository"; import { Address, Registry, @@ -171,10 +171,8 @@ export async function sendFailedStatusReport( } } +/** The basename of the proxy artifacts. */ export const UPDATEJOB_PROXY = "update-job-proxy"; -export const UPDATEJOB_PROXY_VERSION = "v2.0.20250624110901"; -const UPDATEJOB_PROXY_URL_PREFIX = - "https://github.com/github/codeql-action/releases/download/codeql-bundle-v2.22.0/"; function isPAT(value: string) { return artifactScanner.isAuthToken(value, [ @@ -379,30 +377,6 @@ export function getProxyPackage(platform: BundlePlatform): string { return `${UPDATEJOB_PROXY}-${platform}.tar.gz`; } -/** - * Gets the fallback URL for downloading the proxy release asset. - * - * @param proxyPackage The asset name. - * @returns The full URL to download the specified asset from the fallback release. - */ -export function getFallbackUrl(proxyPackage: string): string { - return `${UPDATEJOB_PROXY_URL_PREFIX}${proxyPackage}`; -} - -/** - * Uses the GitHub API to obtain information about the CodeQL CLI bundle release - * that is tagged by `version`. - * - * @returns The response from the GitHub API. - */ -async function getReleaseByVersion(version: string) { - return getApiClient().rest.repos.getReleaseByTag({ - owner: "github", - repo: "codeql-action", - tag: version, - }); -} - /** Uses `features` to determine the default CLI version. */ async function getCliVersionFromFeatures( features: FeatureEnablement, @@ -419,7 +393,8 @@ async function getCliVersionFromFeatures( * @returns Returns the download URL and version of the proxy package we plan to use. */ export async function getDownloadUrl( - action: ActionState<["Base", "Logger", "FeatureFlags"]>, + action: ActionState<["Base", "ReadOnlyEnv", "Logger", "FeatureFlags"]>, + apiDetails: GitHubApiDetails, ): Promise<{ url: string; version: string }> { // Default to linux64 if we don't recognise the platform+arch pair. // This maintains the behaviour we had before switching to `getBundlePlatform` here. @@ -448,37 +423,26 @@ export async function getDownloadUrl( }; // Try to retrieve information about the CLI bundle release identified by `versionInfo`. - const cliRelease = await getReleaseByVersion(versionInfo.tagName); + const proxyAssetUrl = await getCodeQLAssetDownloadURL( + action, + apiDetails, + versionInfo.tagName, + proxyPackage, + "private registry proxy", + ); - // Search the release's assets to find the one we are looking for. - for (const asset of cliRelease.data.assets) { - if (asset.name === proxyPackage) { - action.logger.info( - `Found '${proxyPackage}' in release '${versionInfo.tagName}' at '${asset.url}'`, - ); - return { - url: asset.url, - // The `update-job-proxy` doesn't have a version as such. Since we now bundle it - // with CodeQL CLI bundle releases, we use the corresponding CLI version to - // differentiate between (potentially) different versions of `update-job-proxy`. - version: versionInfo.cliVersion, - }; - } - } + return { + url: proxyAssetUrl, + // The `update-job-proxy` doesn't have a version as such. Since we now bundle it + // with CodeQL CLI bundle releases, we use the corresponding CLI version to + // differentiate between (potentially) different versions of `update-job-proxy`. + version: versionInfo.cliVersion, + }; } catch (ex) { - action.logger.warning( + throw new Error( `Failed to retrieve information about the linked release: ${getErrorMessage(ex)}`, ); } - - // Fallback to the hard-coded URL. - action.logger.info( - `Did not find '${proxyPackage}' in the linked release, falling back to hard-coded version.`, - ); - return { - url: getFallbackUrl(proxyPackage), - version: UPDATEJOB_PROXY_VERSION, - }; } /** @@ -569,15 +533,15 @@ export function getProxyFilename() { * @returns The path to the proxy binary. */ export async function getProxyBinaryPath( - action: ActionState<["Base", "Logger", "FeatureFlags"]>, + action: ActionState<["Base", "ReadOnlyEnv", "Logger", "FeatureFlags"]>, + apiDetails: GitHubApiDetails, ): Promise { const logger = action.logger; const proxyFileName = getProxyFilename(); - const proxyInfo = await getDownloadUrl(action); + const proxyInfo = await getDownloadUrl(action, apiDetails); let proxyBin = toolcache.find(proxyFileName, proxyInfo.version); if (!proxyBin) { - const apiDetails = getApiDetails(); const authorization = getAuthorizationHeaderFor( logger, apiDetails, From f9a9962aa30c39b96d0879722aeb8c3fb5bb9bbf Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Tue, 6 Oct 2026 17:26:09 +0100 Subject: [PATCH 09/25] Fix `onceWith` not asserting the `once` part --- src/testing-utils.ts | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/src/testing-utils.ts b/src/testing-utils.ts index 2b188857a9..4e8d175200 100644 --- a/src/testing-utils.ts +++ b/src/testing-utils.ts @@ -179,13 +179,25 @@ export function addSinonAssertions( ): ExecutionContext & AvaSinonAssertions { return { ...t, - onceWith: (stub, ...expected) => { - if (stub.callCount > 0) { - // Reduce the arguments of the first call to the same number that was provided, - // so that extra arguments don't lead to a failure. - return t.deepEqual(stub.args[0].slice(0, expected.length), expected); + onceWith: ( + stub: sinon.SinonStub, + ...expected: sinon.MatchPartialArguments + ) => { + // Fail if the stub hasn't been called at all so that we can safely compare + // the arguments of the first call in the else branch. + if (stub.callCount === 0) { + t.fail("The stub wasn't called."); } else { - return t.fail("The stub wasn't called."); + // Reduce the arguments of each to the same number that was provided, + // so that extra arguments don't lead to a failure. + const actual = stub.args.map>( + (args: TArgs) => + args.slice( + 0, + expected.length, + ) as sinon.MatchPartialArguments, + ); + return t.deepEqual(actual, [expected]); } }, }; From c1b620cc0e7973d9e01f4777f33bdafdbb570868 Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Mon, 12 Oct 2026 14:56:00 +0100 Subject: [PATCH 10/25] Allow `initializeEnvironment` to accept `Env` --- lib/entry-points.js | 12 ++++++------ src/setup/repository.test.ts | 11 ++++++----- src/util.ts | 14 ++++++++------ 3 files changed, 20 insertions(+), 17 deletions(-) diff --git a/lib/entry-points.js b/lib/entry-points.js index 846ab37e40..9f49031d9e 100644 --- a/lib/entry-points.js +++ b/lib/entry-points.js @@ -169810,12 +169810,12 @@ var ExhaustivityCheckingError = class extends Error { function assertNever(value) { throw new ExhaustivityCheckingError(value); } -function initializeEnvironment(version) { - exportEnvVar("CODEQL_ACTION_FEATURE_MULTI_LANGUAGE" /* FEATURE_MULTI_LANGUAGE */, "false"); - exportEnvVar("CODEQL_ACTION_FEATURE_SANDWICH" /* FEATURE_SANDWICH */, "false"); - exportEnvVar("CODEQL_ACTION_FEATURE_SARIF_COMBINE" /* FEATURE_SARIF_COMBINE */, "true"); - exportEnvVar("CODEQL_ACTION_FEATURE_WILL_UPLOAD" /* FEATURE_WILL_UPLOAD */, "true"); - exportEnvVar("CODEQL_ACTION_VERSION" /* VERSION */, version); +function initializeEnvironment(version, env = getEnv()) { + env.export("CODEQL_ACTION_FEATURE_MULTI_LANGUAGE" /* FEATURE_MULTI_LANGUAGE */, "false"); + env.export("CODEQL_ACTION_FEATURE_SANDWICH" /* FEATURE_SANDWICH */, "false"); + env.export("CODEQL_ACTION_FEATURE_SARIF_COMBINE" /* FEATURE_SARIF_COMBINE */, "true"); + env.export("CODEQL_ACTION_FEATURE_WILL_UPLOAD" /* FEATURE_WILL_UPLOAD */, "true"); + env.export("CODEQL_ACTION_VERSION" /* VERSION */, version); } var HTTPError = class extends Error { status; diff --git a/src/setup/repository.test.ts b/src/setup/repository.test.ts index 56eb74786f..d1c6d034c0 100644 --- a/src/setup/repository.test.ts +++ b/src/setup/repository.test.ts @@ -5,7 +5,7 @@ import * as sinon from "sinon"; import * as actionsUtil from "../actions-util"; import { ActionsEnvVars } from "../environment"; -import { callee, setupTests } from "../testing-utils"; +import { callee, getTestEnv, setupTests } from "../testing-utils"; import { initializeEnvironment } from "../util"; import { getCodeQLActionRepository } from "./repository"; @@ -13,13 +13,14 @@ import { getCodeQLActionRepository } from "./repository"; setupTests(test); test.serial("getCodeQLActionRepository", async (t) => { - initializeEnvironment("1.2.3"); + const initialEnv = getTestEnv({ + [ActionsEnvVars.RUNNER_TEMP]: path.dirname(__dirname), + }); + initializeEnvironment("1.2.3", initialEnv); const target = callee(getCodeQLActionRepository) .withArgs() - .withEnv((env) => { - env.set(ActionsEnvVars.RUNNER_TEMP, path.dirname(__dirname)); - }); + .withEnv(initialEnv); // isRunningLocalAction() === true await target.passes(t.deepEqual, "github/codeql-action"); diff --git a/src/util.ts b/src/util.ts index 674e2a2eef..3a42d8b945 100644 --- a/src/util.ts +++ b/src/util.ts @@ -19,6 +19,8 @@ import { getRequiredEnvParam, isInTestMode, exportEnvVar, + getEnv, + type Env, } from "./environment"; import * as json from "./json"; import { Language } from "./languages"; @@ -595,12 +597,12 @@ export function assertNever(value: never): never { * Set some initial environment variables that we can set even without * knowing what version of CodeQL we're running. */ -export function initializeEnvironment(version: string) { - exportEnvVar(EnvVar.FEATURE_MULTI_LANGUAGE, "false"); - exportEnvVar(EnvVar.FEATURE_SANDWICH, "false"); - exportEnvVar(EnvVar.FEATURE_SARIF_COMBINE, "true"); - exportEnvVar(EnvVar.FEATURE_WILL_UPLOAD, "true"); - exportEnvVar(EnvVar.VERSION, version); +export function initializeEnvironment(version: string, env: Env = getEnv()) { + env.export(EnvVar.FEATURE_MULTI_LANGUAGE, "false"); + env.export(EnvVar.FEATURE_SANDWICH, "false"); + env.export(EnvVar.FEATURE_SARIF_COMBINE, "true"); + env.export(EnvVar.FEATURE_WILL_UPLOAD, "true"); + env.export(EnvVar.VERSION, version); } export class HTTPError extends Error { From ad8ebce2932d8d44604dd6430e4fae7c3ff55873 Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Mon, 12 Oct 2026 15:27:32 +0100 Subject: [PATCH 11/25] Add and use `RepositoryNwoStr` type --- lib/entry-points.js | 13 ++++++++++++- src/repository.ts | 8 ++++++++ src/setup/repository.ts | 19 +++++++++++++++---- 3 files changed, 35 insertions(+), 5 deletions(-) diff --git a/lib/entry-points.js b/lib/entry-points.js index 9f49031d9e..99b2b4fc2c 100644 --- a/lib/entry-points.js +++ b/lib/entry-points.js @@ -170631,6 +170631,9 @@ retry.VERSION = VERSION7; var import_undici = __toESM(require_undici()); // src/repository.ts +function isRepositoryNwoStr(val) { + return val.includes("/"); +} function getRepositoryNwo() { return getRepositoryNwoFromEnv("GITHUB_REPOSITORY"); } @@ -175669,7 +175672,15 @@ function getCodeQLActionRepository(action) { ); return CODEQL_DEFAULT_ACTION_REPOSITORY; } - return action.env.getRequired("GITHUB_ACTION_REPOSITORY" /* GITHUB_ACTION_REPOSITORY */); + const actionRepository = action.env.getRequired( + "GITHUB_ACTION_REPOSITORY" /* GITHUB_ACTION_REPOSITORY */ + ); + if (isRepositoryNwoStr(actionRepository)) { + return actionRepository; + } + throw new Error( + `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, diff --git a/src/repository.ts b/src/repository.ts index f1e7f369bc..652521fe33 100644 --- a/src/repository.ts +++ b/src/repository.ts @@ -6,6 +6,14 @@ export interface RepositoryNwo { repo: string; } +/** An approximation of what a repository NWO string looks like. */ +export type RepositoryNwoStr = `${string}/${string}`; + +/** A basic check that `val` looks like a `RepositoryNwoStr`. */ +export function isRepositoryNwoStr(val: string): val is RepositoryNwoStr { + return val.includes("/"); +} + /** * Get the repository name with owner from the environment variable * `GITHUB_REPOSITORY`. diff --git a/src/setup/repository.ts b/src/setup/repository.ts index 3d760858e7..98165ea306 100644 --- a/src/setup/repository.ts +++ b/src/setup/repository.ts @@ -2,6 +2,7 @@ import { ActionState } from "../action-common"; import { isRunningLocalAction } from "../actions-util"; import * as api from "../api-client"; import { ActionsEnvVars } from "../environment"; +import { isRepositoryNwoStr, type RepositoryNwoStr } from "../repository"; import { GITHUB_DOTCOM_URL } from "../util"; /** The NWO of the standard CodeQL Action repository. */ @@ -17,7 +18,7 @@ export const CODEQL_DEFAULT_ACTION_REPOSITORY = "github/codeql-action"; */ export function getCodeQLActionRepository( action: ActionState<["ReadOnlyEnv", "Logger"]>, -): string { +): RepositoryNwoStr { if (isRunningLocalAction(action.env)) { // This handles the case where the Action does not come from an Action repository, // e.g. our integration tests which use the Action code from the current checkout. @@ -28,11 +29,21 @@ export function getCodeQLActionRepository( return CODEQL_DEFAULT_ACTION_REPOSITORY; } - return action.env.getRequired(ActionsEnvVars.GITHUB_ACTION_REPOSITORY); + const actionRepository = action.env.getRequired( + ActionsEnvVars.GITHUB_ACTION_REPOSITORY, + ); + + if (isRepositoryNwoStr(actionRepository)) { + return actionRepository; + } + + throw new Error( + `The value of '${ActionsEnvVars.GITHUB_ACTION_REPOSITORY}' is '${actionRepository}', which does not match the expected 'owner/repo' format.`, + ); } /**A download source is a pair of strings. */ -export type DownloadSource = [string, string]; +export type DownloadSource = [string, RepositoryNwoStr]; /** GitHub.com, and the canonical Action. */ export const DEFAULT_DOWNLOAD_SOURCE: DownloadSource = [ @@ -88,7 +99,7 @@ export async function getCodeQLAssetDownloadURL( ): Promise { const codeQLActionRepository = getCodeQLActionRepository(action); - const potentialDownloadSources: Array<[string, string]> = [ + const potentialDownloadSources: Array<[string, RepositoryNwoStr]> = [ // This GitHub instance, and this Action. [apiDetails.url, codeQLActionRepository], // This GitHub instance, and the canonical Action. From 1a92e13c5e077a3c134060c2c5042760c9d9ae42 Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Mon, 12 Oct 2026 15:28:08 +0100 Subject: [PATCH 12/25] Use `GITHUB_DOTCOM_URL` in `getDefaultDotComDownloadURL` --- lib/entry-points.js | 2 +- src/setup/repository.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/entry-points.js b/lib/entry-points.js index 99b2b4fc2c..507d2ddf54 100644 --- a/lib/entry-points.js +++ b/lib/entry-points.js @@ -175690,7 +175690,7 @@ var isSameDownloadSource = ([srcUrl, srcRepo], [otherUrl, otherRepo]) => { return srcUrl === otherUrl && srcRepo === otherRepo; }; function getDefaultDotComDownloadURL(tagName, assetName) { - return `https://github.com/${CODEQL_DEFAULT_ACTION_REPOSITORY}/releases/download/${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); diff --git a/src/setup/repository.ts b/src/setup/repository.ts index 98165ea306..0cdfaa1738 100644 --- a/src/setup/repository.ts +++ b/src/setup/repository.ts @@ -67,7 +67,7 @@ export function getDefaultDotComDownloadURL( tagName: string, assetName: string, ): DownloadURL { - return `https://github.com/${CODEQL_DEFAULT_ACTION_REPOSITORY}/releases/download/${tagName}/${assetName}`; + return `${GITHUB_DOTCOM_URL}/${CODEQL_DEFAULT_ACTION_REPOSITORY}/releases/download/${tagName}/${assetName}`; } /** Enumerates log-friendly descriptions of different kinds of assets we might look for in releases. */ From e93db40ccef689468ceaf388bd289a07b47c052d Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Mon, 12 Oct 2026 15:28:55 +0100 Subject: [PATCH 13/25] Use "asset" in comment --- src/setup/repository.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/setup/repository.ts b/src/setup/repository.ts index 0cdfaa1738..004beb1dc1 100644 --- a/src/setup/repository.ts +++ b/src/setup/repository.ts @@ -119,7 +119,7 @@ export async function getCodeQLAssetDownloadURL( ); for (const [apiURL, repository] of uniqueDownloadSources) { - // If we've reached the final case, short-circuit the API check since we know the bundle exists and is public. + // 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, [apiURL, repository])) { break; } From fdc069b4ec7f588ddf76a7e600213d2112842d04 Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Mon, 12 Oct 2026 15:29:50 +0100 Subject: [PATCH 14/25] Use `DownloadSource` for `potentialDownloadSources` --- src/setup/repository.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/setup/repository.ts b/src/setup/repository.ts index 004beb1dc1..f4e69797ec 100644 --- a/src/setup/repository.ts +++ b/src/setup/repository.ts @@ -99,7 +99,7 @@ export async function getCodeQLAssetDownloadURL( ): Promise { const codeQLActionRepository = getCodeQLActionRepository(action); - const potentialDownloadSources: Array<[string, RepositoryNwoStr]> = [ + const potentialDownloadSources: DownloadSource[] = [ // This GitHub instance, and this Action. [apiDetails.url, codeQLActionRepository], // This GitHub instance, and the canonical Action. From 06bd6e2f9d18767c683a39083f38deaa106e46ce Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Mon, 12 Oct 2026 15:31:11 +0100 Subject: [PATCH 15/25] `DotCom` => `Dotcom` --- lib/entry-points.js | 4 ++-- src/setup/repository.ts | 4 ++-- src/start-proxy.test.ts | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/lib/entry-points.js b/lib/entry-points.js index 507d2ddf54..a6e80b84ea 100644 --- a/lib/entry-points.js +++ b/lib/entry-points.js @@ -175689,7 +175689,7 @@ var DEFAULT_DOWNLOAD_SOURCE = [ var isSameDownloadSource = ([srcUrl, srcRepo], [otherUrl, otherRepo]) => { return srcUrl === otherUrl && srcRepo === otherRepo; }; -function getDefaultDotComDownloadURL(tagName, assetName) { +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") { @@ -175732,7 +175732,7 @@ async function getCodeQLAssetDownloadURL(action, apiDetails, tagName, assetName, ); } } - return getDefaultDotComDownloadURL(tagName, assetName); + return getDefaultDotcomDownloadURL(tagName, assetName); } // src/tar.ts diff --git a/src/setup/repository.ts b/src/setup/repository.ts index f4e69797ec..636787024d 100644 --- a/src/setup/repository.ts +++ b/src/setup/repository.ts @@ -63,7 +63,7 @@ const isSameDownloadSource = ( export type DownloadURL = string; /** Gets the default GitHub.com download URL for `assetName` in a release tagged `tagName`. */ -export function getDefaultDotComDownloadURL( +export function getDefaultDotcomDownloadURL( tagName: string, assetName: string, ): DownloadURL { @@ -147,5 +147,5 @@ export async function getCodeQLAssetDownloadURL( } } - return getDefaultDotComDownloadURL(tagName, assetName); + return getDefaultDotcomDownloadURL(tagName, assetName); } diff --git a/src/start-proxy.test.ts b/src/start-proxy.test.ts index b0bdd7bd33..ed2007fe61 100644 --- a/src/start-proxy.test.ts +++ b/src/start-proxy.test.ts @@ -780,7 +780,7 @@ test.serial( t.is(info.version, defaults.cliVersion); t.is( info.url, - repository.getDefaultDotComDownloadURL( + repository.getDefaultDotcomDownloadURL( defaults.bundleVersion, startProxyExports.getProxyPackage(BundlePlatform.Linux64), ), @@ -810,7 +810,7 @@ test.serial( t.is(info.version, defaults.cliVersion); t.is( info.url, - repository.getDefaultDotComDownloadURL( + repository.getDefaultDotcomDownloadURL( defaults.bundleVersion, startProxyExports.getProxyPackage(getTestPlatform(state)), ), @@ -842,7 +842,7 @@ test.serial( t.is(info.version, defaults.cliVersion); t.is( info.url, - repository.getDefaultDotComDownloadURL( + repository.getDefaultDotcomDownloadURL( defaults.bundleVersion, startProxyExports.getProxyPackage(getTestPlatform(state)), ), From 3d7016f5140155c81cbea404bdd435836d97ae56 Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Mon, 12 Oct 2026 15:39:38 +0100 Subject: [PATCH 16/25] Log more in `getProxyBinaryPath` --- lib/entry-points.js | 47 +++++++++++++++++++++++++-------------- src/start-proxy.ts | 54 +++++++++++++++++++++++++++++++-------------- 2 files changed, 67 insertions(+), 34 deletions(-) diff --git a/lib/entry-points.js b/lib/entry-points.js index a6e80b84ea..a2951f159c 100644 --- a/lib/entry-points.js +++ b/lib/entry-points.js @@ -187759,25 +187759,38 @@ function getProxyFilename() { } async function getProxyBinaryPath(action, apiDetails) { const logger2 = action.logger; - const proxyFileName = getProxyFilename(); - const proxyInfo = await getDownloadUrl(action, apiDetails); - let proxyBin = toolcache4.find(proxyFileName, proxyInfo.version); - if (!proxyBin) { - const authorization = getAuthorizationHeaderFor( - logger2, - apiDetails, - proxyInfo.url - ); - const temp = await downloadProxy(logger2, proxyInfo.url, authorization); - const extracted = await extractProxy(logger2, temp); - proxyBin = await cacheProxy( - logger2, - extracted, - proxyFileName, - proxyInfo.version + logger2.startGroup("Setting up authentication proxy"); + try { + const proxyFileName = getProxyFilename(); + const proxyInfo = await getDownloadUrl(action, apiDetails); + logger2.info( + `Using authentication proxy from CodeQL release ${proxyInfo.version}.` ); + let proxyBin = toolcache4.find(proxyFileName, proxyInfo.version); + if (!proxyBin) { + logger2.info( + `Authentication proxy is not in the toolcache. Will try to download it from '${proxyInfo.url}'.` + ); + const authorization = getAuthorizationHeaderFor( + logger2, + apiDetails, + proxyInfo.url + ); + const temp = await downloadProxy(logger2, proxyInfo.url, authorization); + logger2.info(`Downloaded the authentication proxy assset to '${temp}'.`); + const extracted = await extractProxy(logger2, temp); + proxyBin = await cacheProxy( + logger2, + extracted, + proxyFileName, + proxyInfo.version + ); + logger2.info(`Extracted the authentication proxy to '${proxyBin}'.`); + } + return path27.join(proxyBin, proxyFileName); + } finally { + logger2.endGroup(); } - return path27.join(proxyBin, proxyFileName); } // src/start-proxy/ca.ts diff --git a/src/start-proxy.ts b/src/start-proxy.ts index 9558b119ee..ca2f5c2b5b 100644 --- a/src/start-proxy.ts +++ b/src/start-proxy.ts @@ -537,24 +537,44 @@ export async function getProxyBinaryPath( apiDetails: GitHubApiDetails, ): Promise { const logger = action.logger; - const proxyFileName = getProxyFilename(); - const proxyInfo = await getDownloadUrl(action, apiDetails); + logger.startGroup("Setting up authentication proxy"); - let proxyBin = toolcache.find(proxyFileName, proxyInfo.version); - if (!proxyBin) { - const authorization = getAuthorizationHeaderFor( - logger, - apiDetails, - proxyInfo.url, - ); - const temp = await downloadProxy(logger, proxyInfo.url, authorization); - const extracted = await extractProxy(logger, temp); - proxyBin = await cacheProxy( - logger, - extracted, - proxyFileName, - proxyInfo.version, + try { + const proxyFileName = getProxyFilename(); + const proxyInfo = await getDownloadUrl(action, apiDetails); + + logger.info( + `Using authentication proxy from CodeQL release ${proxyInfo.version}.`, ); + + let proxyBin = toolcache.find(proxyFileName, proxyInfo.version); + + if (!proxyBin) { + logger.info( + `Authentication proxy is not in the toolcache. Will try to download it from '${proxyInfo.url}'.`, + ); + + const authorization = getAuthorizationHeaderFor( + logger, + apiDetails, + proxyInfo.url, + ); + const temp = await downloadProxy(logger, proxyInfo.url, authorization); + + logger.info(`Downloaded the authentication proxy assset to '${temp}'.`); + + const extracted = await extractProxy(logger, temp); + proxyBin = await cacheProxy( + logger, + extracted, + proxyFileName, + proxyInfo.version, + ); + + logger.info(`Extracted the authentication proxy to '${proxyBin}'.`); + } + return path.join(proxyBin, proxyFileName); + } finally { + logger.endGroup(); } - return path.join(proxyBin, proxyFileName); } From dd3898ae5842543c91979b8a9b9b3f47bfdee874 Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Mon, 12 Oct 2026 15:40:20 +0100 Subject: [PATCH 17/25] Await `toolcache.downloadTool` --- lib/entry-points.js | 2 +- src/start-proxy.ts | 11 ++++++++--- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/lib/entry-points.js b/lib/entry-points.js index a2951f159c..f22ad311fc 100644 --- a/lib/entry-points.js +++ b/lib/entry-points.js @@ -187718,7 +187718,7 @@ async function getDownloadUrl(action, apiDetails) { } async function downloadProxy(logger2, url2, authorization) { try { - return toolcache4.downloadTool( + return await toolcache4.downloadTool( url2, /* dest: */ void 0, diff --git a/src/start-proxy.ts b/src/start-proxy.ts index ca2f5c2b5b..21a5012f12 100644 --- a/src/start-proxy.ts +++ b/src/start-proxy.ts @@ -461,9 +461,14 @@ export async function downloadProxy( try { // Download the proxy archive from `url`. We let `downloadTool` choose where // to store it. The path to the downloaded file will be returned if successful. - return toolcache.downloadTool(url, /* dest: */ undefined, authorization, { - accept: "application/octet-stream", - }); + return await toolcache.downloadTool( + url, + /* dest: */ undefined, + authorization, + { + accept: "application/octet-stream", + }, + ); } catch (error) { logger.error( `Failed to download proxy archive from ${url}: ${getErrorMessage(error)}`, From ac44fd090137600c00845d6ea3297cda7fefdad7 Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Mon, 12 Oct 2026 15:41:25 +0100 Subject: [PATCH 18/25] Remove duplicated assertions --- src/start-proxy.test.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/start-proxy.test.ts b/src/start-proxy.test.ts index ed2007fe61..b8de01c07e 100644 --- a/src/start-proxy.test.ts +++ b/src/start-proxy.test.ts @@ -1079,8 +1079,6 @@ test.serial( st.onceWith(extractTar, archivePath); st.onceWith(cacheDir, extractedPath); - t.assert(extractTar.calledOnceWith(archivePath)); - t.assert(cacheDir.calledOnceWith(extractedPath)); t.assert(path); t.is( path, From a1794a03ffd7b772215b9612f0024299630d2c6c Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Mon, 12 Oct 2026 15:45:21 +0100 Subject: [PATCH 19/25] Fix `mockGetReleaseByTag` usage --- src/start-proxy.test.ts | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/src/start-proxy.test.ts b/src/start-proxy.test.ts index b8de01c07e..d01543e709 100644 --- a/src/start-proxy.test.ts +++ b/src/start-proxy.test.ts @@ -729,7 +729,10 @@ function mockGetReleaseByTag(expectedTag?: string, assets?: ReleaseAssets) { } }); - return mockGetApiClient({ repos: { getReleaseByTag } }); + return { + getReleaseByTag, + getApiClient: mockGetApiClient({ repos: { getReleaseByTag } }), + }; } function mockOfflineFeatures(tempDir: string, logger: Logger) { @@ -833,7 +836,7 @@ test.serial( sinon.stub(actionsUtil, "isRunningLocalAction").returns(true); for (const assets of testAssets) { - const stub = mockGetReleaseByTag(defaults.bundleVersion, assets); + const stubs = mockGetReleaseByTag(defaults.bundleVersion, assets); const info = await startProxyExports.getDownloadUrl( state, SAMPLE_DOTCOM_API_DETAILS, @@ -848,7 +851,9 @@ test.serial( ), ); - stub.restore(); + // mockGetReleaseByTag stubs the global getApiClient; since we are calling + // that in a loop, we need to manually restore it here + stubs.getApiClient.restore(); } }); }, @@ -864,7 +869,7 @@ test.serial("getDownloadUrl returns matching release asset", async (t) => { url: "url-we-want", }, ]; - const getReleaseByTag = mockGetReleaseByTag(defaults.bundleVersion, assets); + const stubs = mockGetReleaseByTag(defaults.bundleVersion, assets); await withTmpDir(async (tempDir) => { state.env.set(ActionsEnvVars.RUNNER_TEMP, tempDir); @@ -879,7 +884,7 @@ test.serial("getDownloadUrl returns matching release asset", async (t) => { SAMPLE_DOTCOM_API_DETAILS, ); - t.is(getReleaseByTag.callCount, 1); + t.is(stubs.getReleaseByTag.callCount, 1); t.is(info.version, defaults.cliVersion); t.is(info.url, "url-we-want"); @@ -1036,7 +1041,7 @@ test.serial( const logger = new RecordingLogger(); const state = initAllState({ logger }); const downloadUrl = "url-we-want"; - const getApiClient = mockGetReleaseByTag(defaults.bundleVersion, [ + const stubs = mockGetReleaseByTag(defaults.bundleVersion, [ { name: startProxyExports.getProxyPackage(getTestPlatform(state)), url: downloadUrl, @@ -1070,7 +1075,7 @@ test.serial( SAMPLE_DOTCOM_API_DETAILS, ); - t.is(getApiClient.callCount, 1); + t.is(stubs.getReleaseByTag.callCount, 1); t.assert(find.calledOnce); t.assert(getAuthorizationHeaderFor.calledOnce); From 8fed29eed7c56f2ad5ff4886a0b5bac4a7d60868 Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Mon, 12 Oct 2026 15:47:52 +0100 Subject: [PATCH 20/25] Rename `apiURL` to `instanceURL` --- lib/entry-points.js | 8 ++++---- src/setup/repository.ts | 10 ++++++---- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/lib/entry-points.js b/lib/entry-points.js index f22ad311fc..6e080eaa5d 100644 --- a/lib/entry-points.js +++ b/lib/entry-points.js @@ -175707,8 +175707,8 @@ async function getCodeQLAssetDownloadURL(action, apiDetails, tagName, assetName, return !self2.slice(0, index2).some((other) => isSameDownloadSource(source, other)); } ); - for (const [apiURL, repository] of uniqueDownloadSources) { - if (isSameDownloadSource(DEFAULT_DOWNLOAD_SOURCE, [apiURL, repository])) { + for (const [instanceURL, repository] of uniqueDownloadSources) { + if (isSameDownloadSource(DEFAULT_DOWNLOAD_SOURCE, [instanceURL, repository])) { break; } const [repositoryOwner, repositoryName] = repository.split("/"); @@ -175721,14 +175721,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 ${apiURL} with URL ${asset.url}.` + `Found ${assetKind} ${assetName} in ${repository} on ${instanceURL} with URL ${asset.url}.` ); return asset.url; } } } catch (e) { action.logger.info( - `Looked for ${assetKind} ${assetName} in ${repository} on ${apiURL} but got error ${e}.` + `Looked for ${assetKind} ${assetName} in ${repository} on ${instanceURL} but got error ${e}.` ); } } diff --git a/src/setup/repository.ts b/src/setup/repository.ts index 636787024d..2e53872974 100644 --- a/src/setup/repository.ts +++ b/src/setup/repository.ts @@ -118,9 +118,11 @@ export async function getCodeQLAssetDownloadURL( }, ); - for (const [apiURL, repository] of uniqueDownloadSources) { + for (const [instanceURL, 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, [apiURL, repository])) { + if ( + isSameDownloadSource(DEFAULT_DOWNLOAD_SOURCE, [instanceURL, repository]) + ) { break; } @@ -135,14 +137,14 @@ export async function getCodeQLAssetDownloadURL( for (const asset of release.data.assets) { if (asset.name === assetName) { action.logger.info( - `Found ${assetKind} ${assetName} in ${repository} on ${apiURL} with URL ${asset.url}.`, + `Found ${assetKind} ${assetName} in ${repository} on ${instanceURL} with URL ${asset.url}.`, ); return asset.url; } } } catch (e) { action.logger.info( - `Looked for ${assetKind} ${assetName} in ${repository} on ${apiURL} but got error ${e}.`, + `Looked for ${assetKind} ${assetName} in ${repository} on ${instanceURL} but got error ${e}.`, ); } } From 5aa103d050a3985a691d3cb0c4d2f1055d523cd1 Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Mon, 12 Oct 2026 15:52:22 +0100 Subject: [PATCH 21/25] Add `InstanceURL` and use in `DownloadSource` --- src/setup/repository.ts | 4 ++-- src/util.ts | 5 ++++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/setup/repository.ts b/src/setup/repository.ts index 2e53872974..959ca4b8d7 100644 --- a/src/setup/repository.ts +++ b/src/setup/repository.ts @@ -3,7 +3,7 @@ import { isRunningLocalAction } from "../actions-util"; import * as api from "../api-client"; import { ActionsEnvVars } from "../environment"; import { isRepositoryNwoStr, type RepositoryNwoStr } from "../repository"; -import { GITHUB_DOTCOM_URL } from "../util"; +import { GITHUB_DOTCOM_URL, type InstanceURL } from "../util"; /** The NWO of the standard CodeQL Action repository. */ export const CODEQL_DEFAULT_ACTION_REPOSITORY = "github/codeql-action"; @@ -43,7 +43,7 @@ export function getCodeQLActionRepository( } /**A download source is a pair of strings. */ -export type DownloadSource = [string, RepositoryNwoStr]; +export type DownloadSource = [InstanceURL, RepositoryNwoStr]; /** GitHub.com, and the canonical Action. */ export const DEFAULT_DOWNLOAD_SOURCE: DownloadSource = [ diff --git a/src/util.ts b/src/util.ts index 3a42d8b945..0b10e0f043 100644 --- a/src/util.ts +++ b/src/util.ts @@ -46,10 +46,13 @@ const BASE_DATABASE_OIDS_FILE_NAME = "base-database-oids.json"; */ const BROKEN_VERSIONS = ["0.0.0-20211207"]; +/** A string representing a GitHub instance. */ +export type InstanceURL = string; + /** * The URL for github.com. */ -export const GITHUB_DOTCOM_URL = "https://github.com"; +export const GITHUB_DOTCOM_URL: InstanceURL = "https://github.com"; /** * Default name of the debugging artifact. From b67b5abe29b2f646e6cb9afe110070e2f422b873 Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Tue, 13 Oct 2026 12:13:59 +0100 Subject: [PATCH 22/25] Use `parseRepositoryNwo` in `getCodeQLAssetDownloadURL` --- lib/entry-points.js | 2 +- src/setup/repository.ts | 9 +++++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/lib/entry-points.js b/lib/entry-points.js index 6e080eaa5d..30df46a5b9 100644 --- a/lib/entry-points.js +++ b/lib/entry-points.js @@ -175711,7 +175711,7 @@ async function getCodeQLAssetDownloadURL(action, apiDetails, tagName, assetName, if (isSameDownloadSource(DEFAULT_DOWNLOAD_SOURCE, [instanceURL, repository])) { break; } - const [repositoryOwner, repositoryName] = repository.split("/"); + const { owner: repositoryOwner, repo: repositoryName } = parseRepositoryNwo(repository); try { const release2 = await getApiClient().rest.repos.getReleaseByTag({ owner: repositoryOwner, diff --git a/src/setup/repository.ts b/src/setup/repository.ts index 959ca4b8d7..6da0ea3e9a 100644 --- a/src/setup/repository.ts +++ b/src/setup/repository.ts @@ -2,7 +2,11 @@ import { ActionState } from "../action-common"; import { isRunningLocalAction } from "../actions-util"; import * as api from "../api-client"; import { ActionsEnvVars } from "../environment"; -import { isRepositoryNwoStr, type RepositoryNwoStr } from "../repository"; +import { + isRepositoryNwoStr, + parseRepositoryNwo, + type RepositoryNwoStr, +} from "../repository"; import { GITHUB_DOTCOM_URL, type InstanceURL } from "../util"; /** The NWO of the standard CodeQL Action repository. */ @@ -126,7 +130,8 @@ export async function getCodeQLAssetDownloadURL( break; } - const [repositoryOwner, repositoryName] = repository.split("/"); + const { owner: repositoryOwner, repo: repositoryName } = + parseRepositoryNwo(repository); try { const release = await api.getApiClient().rest.repos.getReleaseByTag({ owner: repositoryOwner, From 71ad3a794a9edd2ecb4843da2d0d24bab3908a2e Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Wed, 14 Oct 2026 13:57:43 +0100 Subject: [PATCH 23/25] Fix typo in log message --- lib/entry-points.js | 2 +- src/start-proxy.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/entry-points.js b/lib/entry-points.js index e6beeaf6d9..9f141a0780 100644 --- a/lib/entry-points.js +++ b/lib/entry-points.js @@ -187870,7 +187870,7 @@ async function getProxyBinaryPath(action, apiDetails) { proxyInfo.url ); const temp = await downloadProxy(logger2, proxyInfo.url, authorization); - logger2.info(`Downloaded the authentication proxy assset to '${temp}'.`); + logger2.info(`Downloaded the authentication proxy asset to '${temp}'.`); const extracted = await extractProxy(logger2, temp); proxyBin = await cacheProxy( logger2, diff --git a/src/start-proxy.ts b/src/start-proxy.ts index 21a5012f12..b0cfd6270e 100644 --- a/src/start-proxy.ts +++ b/src/start-proxy.ts @@ -566,7 +566,7 @@ export async function getProxyBinaryPath( ); const temp = await downloadProxy(logger, proxyInfo.url, authorization); - logger.info(`Downloaded the authentication proxy assset to '${temp}'.`); + logger.info(`Downloaded the authentication proxy asset to '${temp}'.`); const extracted = await extractProxy(logger, temp); proxyBin = await cacheProxy( From 962aaad9386547e14df8ca7552245b0a9b3e8218 Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Wed, 14 Oct 2026 13:58:37 +0100 Subject: [PATCH 24/25] Rename `proxyBin` to `proxyDir` --- lib/entry-points.js | 10 +++++----- src/start-proxy.ts | 10 +++++----- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/lib/entry-points.js b/lib/entry-points.js index 9f141a0780..8d6df2f785 100644 --- a/lib/entry-points.js +++ b/lib/entry-points.js @@ -187859,8 +187859,8 @@ async function getProxyBinaryPath(action, apiDetails) { logger2.info( `Using authentication proxy from CodeQL release ${proxyInfo.version}.` ); - let proxyBin = toolcache4.find(proxyFileName, proxyInfo.version); - if (!proxyBin) { + let proxyDir = toolcache4.find(proxyFileName, proxyInfo.version); + if (!proxyDir) { logger2.info( `Authentication proxy is not in the toolcache. Will try to download it from '${proxyInfo.url}'.` ); @@ -187872,15 +187872,15 @@ async function getProxyBinaryPath(action, apiDetails) { const temp = await downloadProxy(logger2, proxyInfo.url, authorization); logger2.info(`Downloaded the authentication proxy asset to '${temp}'.`); const extracted = await extractProxy(logger2, temp); - proxyBin = await cacheProxy( + proxyDir = await cacheProxy( logger2, extracted, proxyFileName, proxyInfo.version ); - logger2.info(`Extracted the authentication proxy to '${proxyBin}'.`); + logger2.info(`Extracted the authentication proxy to '${proxyDir}'.`); } - return path27.join(proxyBin, proxyFileName); + return path27.join(proxyDir, proxyFileName); } finally { logger2.endGroup(); } diff --git a/src/start-proxy.ts b/src/start-proxy.ts index b0cfd6270e..c3826ed6e3 100644 --- a/src/start-proxy.ts +++ b/src/start-proxy.ts @@ -552,9 +552,9 @@ export async function getProxyBinaryPath( `Using authentication proxy from CodeQL release ${proxyInfo.version}.`, ); - let proxyBin = toolcache.find(proxyFileName, proxyInfo.version); + let proxyDir = toolcache.find(proxyFileName, proxyInfo.version); - if (!proxyBin) { + if (!proxyDir) { logger.info( `Authentication proxy is not in the toolcache. Will try to download it from '${proxyInfo.url}'.`, ); @@ -569,16 +569,16 @@ export async function getProxyBinaryPath( logger.info(`Downloaded the authentication proxy asset to '${temp}'.`); const extracted = await extractProxy(logger, temp); - proxyBin = await cacheProxy( + proxyDir = await cacheProxy( logger, extracted, proxyFileName, proxyInfo.version, ); - logger.info(`Extracted the authentication proxy to '${proxyBin}'.`); + logger.info(`Extracted the authentication proxy to '${proxyDir}'.`); } - return path.join(proxyBin, proxyFileName); + return path.join(proxyDir, proxyFileName); } finally { logger.endGroup(); } From 28d5dd981873bfedbe4f650248813f333fe96451 Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Wed, 14 Oct 2026 15:07:40 +0100 Subject: [PATCH 25/25] Revert "Allow logging functions to accept `string[]` directly" This reverts commit 909fda1468f8ef25624848914b92c3faeba7cfe7. --- lib/entry-points.js | 20 ++++---------------- src/logging.test.ts | 17 ----------------- src/logging.ts | 43 ++++++++----------------------------------- src/testing-utils.ts | 14 +++++++------- 4 files changed, 19 insertions(+), 75 deletions(-) delete mode 100644 src/logging.test.ts diff --git a/lib/entry-points.js b/lib/entry-points.js index 8d6df2f785..e56668bbe2 100644 --- a/lib/entry-points.js +++ b/lib/entry-points.js @@ -170475,24 +170475,12 @@ function fixCodeQualityCategory(logger2, category, env = getEnv()) { // src/logging.ts var core5 = __toESM(require_core()); -var joinMessageStrings = (message) => { - if (Array.isArray(message)) { - return message.join(" "); - } else { - return message; - } -}; -var autoJoinMessageStrings = (fn) => { - return (message) => { - return fn(joinMessageStrings(message)); - }; -}; function getActionsLogger() { return { - debug: autoJoinMessageStrings(core5.debug), - info: autoJoinMessageStrings(core5.info), - warning: autoJoinMessageStrings(core5.warning), - error: autoJoinMessageStrings(core5.error), + debug: core5.debug, + info: core5.info, + warning: core5.warning, + error: core5.error, isDebug: core5.isDebug, startGroup: core5.startGroup, endGroup: core5.endGroup diff --git a/src/logging.test.ts b/src/logging.test.ts deleted file mode 100644 index 33f6f8c162..0000000000 --- a/src/logging.test.ts +++ /dev/null @@ -1,17 +0,0 @@ -import test from "ava"; - -import { joinMessageStrings } from "./logging"; -import { setupTests } from "./testing-utils"; - -setupTests(test); - -test("joinMessageStrings", async (t) => { - // For strings and errors, it is the identity function. - t.deepEqual(joinMessageStrings("Hello"), "Hello"); - - const error = new Error("Some error"); - t.deepEqual(joinMessageStrings(error), error); - - // For arrays of strings, we get a join-ed string. - t.deepEqual(joinMessageStrings(["foo", "bar"]), "foo bar"); -}); diff --git a/src/logging.ts b/src/logging.ts index 177aca6c7b..2c34cb54d4 100644 --- a/src/logging.ts +++ b/src/logging.ts @@ -1,16 +1,10 @@ import * as core from "@actions/core"; -/** Types that all of our logging functions accept. */ -export type Loggable = string | string[]; - -/** Some logging functions also accept errors directly. */ -export type LoggableError = Loggable | Error; - export interface Logger { - debug: (message: Loggable) => void; - info: (message: Loggable) => void; - warning: (message: LoggableError) => void; - error: (message: LoggableError) => void; + debug: (message: string) => void; + info: (message: string) => void; + warning: (message: string | Error) => void; + error: (message: string | Error) => void; isDebug: () => boolean; @@ -18,33 +12,12 @@ export interface Logger { endGroup: () => void; } -/** If `message` is an array of strings, the array is `join`-ed into one string separated by spaces. */ -export const joinMessageStrings = ( - message: string[] | string | T, -): string | T => { - if (Array.isArray(message)) { - return message.join(" "); - } else { - return message; - } -}; - -/** - * Wraps a logging function so that `joinMessageStrings` is applied to the input before - * calling the wrapped logging function. - */ -const autoJoinMessageStrings = (fn: (msg: string | T) => void) => { - return (message: string[] | string | T) => { - return fn(joinMessageStrings(message)); - }; -}; - export function getActionsLogger(): Logger { return { - debug: autoJoinMessageStrings(core.debug), - info: autoJoinMessageStrings(core.info), - warning: autoJoinMessageStrings(core.warning), - error: autoJoinMessageStrings(core.error), + debug: core.debug, + info: core.info, + warning: core.warning, + error: core.error, isDebug: core.isDebug, startGroup: core.startGroup, endGroup: core.endGroup, diff --git a/src/testing-utils.ts b/src/testing-utils.ts index 4ff4d49343..d48f9e90f8 100644 --- a/src/testing-utils.ts +++ b/src/testing-utils.ts @@ -30,7 +30,7 @@ import { featureConfig, FeatureEnablement, } from "./feature-flags"; -import { joinMessageStrings, Loggable, LoggableError, Logger } from "./logging"; +import { Logger } from "./logging"; import { OverlayDatabaseMode } from "./overlay/overlay-database-mode"; import { getBundlePlatform } from "./platform"; import { ActionName } from "./status-report"; @@ -658,8 +658,8 @@ export class RecordingLogger implements Logger { constructor(private readonly logToConsole: boolean = true) {} - private addMessage(level: LogLevel, message: LoggableError): void { - this.messages.push({ type: level, message: joinMessageStrings(message) }); + private addMessage(level: LogLevel, message: string | Error): void { + this.messages.push({ type: level, message }); if (this.logToConsole) { // eslint-disable-next-line no-console @@ -684,19 +684,19 @@ export class RecordingLogger implements Logger { return true; } - debug(message: Loggable) { + debug(message: string) { this.addMessage("debug", message); } - info(message: Loggable) { + info(message: string) { this.addMessage("info", message); } - warning(message: LoggableError) { + warning(message: string | Error) { this.addMessage("warning", message); } - error(message: LoggableError) { + error(message: string | Error) { this.addMessage("error", message); }