From 1f9caf0118e0ac28ca8795236cb9d8db9c5c4658 Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Tue, 28 Jul 2026 18:37:01 +0100 Subject: [PATCH 01/35] Refactor `jobRunUuid` init into a function Use in `init` and `setup-codeql` actions --- lib/entry-points.js | 102 +++++++++++++++++++------------------ src/init-action.ts | 6 +-- src/setup-codeql-action.ts | 7 ++- src/status-report.ts | 13 +++++ 4 files changed, 70 insertions(+), 58 deletions(-) diff --git a/lib/entry-points.js b/lib/entry-points.js index 46c44a8183..287d8a127e 100644 --- a/lib/entry-points.js +++ b/lib/entry-points.js @@ -145728,6 +145728,50 @@ function formatDuration(durationMs) { var os3 = __toESM(require("os")); var core7 = __toESM(require_core()); +// node_modules/uuid/dist-node/stringify.js +var byteToHex = []; +for (let i = 0; i < 256; ++i) { + byteToHex.push((i + 256).toString(16).slice(1)); +} +function unsafeStringify(arr, offset = 0) { + return (byteToHex[arr[offset + 0]] + byteToHex[arr[offset + 1]] + byteToHex[arr[offset + 2]] + byteToHex[arr[offset + 3]] + "-" + byteToHex[arr[offset + 4]] + byteToHex[arr[offset + 5]] + "-" + byteToHex[arr[offset + 6]] + byteToHex[arr[offset + 7]] + "-" + byteToHex[arr[offset + 8]] + byteToHex[arr[offset + 9]] + "-" + byteToHex[arr[offset + 10]] + byteToHex[arr[offset + 11]] + byteToHex[arr[offset + 12]] + byteToHex[arr[offset + 13]] + byteToHex[arr[offset + 14]] + byteToHex[arr[offset + 15]]).toLowerCase(); +} + +// node_modules/uuid/dist-node/rng.js +var rnds8 = new Uint8Array(16); +function rng() { + return crypto.getRandomValues(rnds8); +} + +// node_modules/uuid/dist-node/v4.js +function v4(options, buf, offset) { + if (!buf && !options && crypto.randomUUID) { + return crypto.randomUUID(); + } + return _v4(options, buf, offset); +} +function _v4(options, buf, offset) { + options = options || {}; + const rnds = options.random ?? options.rng?.() ?? rng(); + if (rnds.length < 16) { + throw new Error("Random bytes length must be >= 16"); + } + rnds[6] = rnds[6] & 15 | 64; + rnds[8] = rnds[8] & 63 | 128; + if (buf) { + offset = offset || 0; + if (offset < 0 || offset + 16 > buf.length) { + throw new RangeError(`UUID byte range ${offset}:${offset + 15} is out of buffer bounds`); + } + for (let i = 0; i < 16; ++i) { + buf[offset + i] = rnds[i]; + } + return buf; + } + return unsafeStringify(rnds); +} +var v4_default = v4; + // src/api-client.ts var core5 = __toESM(require_core()); var githubUtils = __toESM(require_utils4()); @@ -146346,6 +146390,12 @@ function getDisplayActionName(actionName) { } return actionName; } +function getJobUUID(logger) { + const jobRunUuid = v4_default(); + logger.info(`Job run UUID is ${jobRunUuid}.`); + core7.exportVariable("JOB_RUN_UUID" /* JOB_RUN_UUID */, jobRunUuid); + return jobRunUuid; +} function isFirstPartyAnalysis(actionName) { if (actionName !== "upload-sarif" /* UploadSarif */) { return true; @@ -150068,50 +150118,6 @@ var toolcache3 = __toESM(require_tool_cache()); var import_fast_deep_equal = __toESM(require_fast_deep_equal()); var semver9 = __toESM(require_semver2()); -// node_modules/uuid/dist-node/stringify.js -var byteToHex = []; -for (let i = 0; i < 256; ++i) { - byteToHex.push((i + 256).toString(16).slice(1)); -} -function unsafeStringify(arr, offset = 0) { - return (byteToHex[arr[offset + 0]] + byteToHex[arr[offset + 1]] + byteToHex[arr[offset + 2]] + byteToHex[arr[offset + 3]] + "-" + byteToHex[arr[offset + 4]] + byteToHex[arr[offset + 5]] + "-" + byteToHex[arr[offset + 6]] + byteToHex[arr[offset + 7]] + "-" + byteToHex[arr[offset + 8]] + byteToHex[arr[offset + 9]] + "-" + byteToHex[arr[offset + 10]] + byteToHex[arr[offset + 11]] + byteToHex[arr[offset + 12]] + byteToHex[arr[offset + 13]] + byteToHex[arr[offset + 14]] + byteToHex[arr[offset + 15]]).toLowerCase(); -} - -// node_modules/uuid/dist-node/rng.js -var rnds8 = new Uint8Array(16); -function rng() { - return crypto.getRandomValues(rnds8); -} - -// node_modules/uuid/dist-node/v4.js -function v4(options, buf, offset) { - if (!buf && !options && crypto.randomUUID) { - return crypto.randomUUID(); - } - return _v4(options, buf, offset); -} -function _v4(options, buf, offset) { - options = options || {}; - const rnds = options.random ?? options.rng?.() ?? rng(); - if (rnds.length < 16) { - throw new Error("Random bytes length must be >= 16"); - } - rnds[6] = rnds[6] & 15 | 64; - rnds[8] = rnds[8] & 63 | 128; - if (buf) { - offset = offset || 0; - if (offset < 0 || offset + 16 > buf.length) { - throw new RangeError(`UUID byte range ${offset}:${offset + 15} is out of buffer bounds`); - } - for (let i = 0; i < 16; ++i) { - buf[offset + i] = rnds[i]; - } - return buf; - } - return unsafeStringify(rnds); -} -var v4_default = v4; - // src/overlay/caching.ts var fs10 = __toESM(require("fs")); var actionsCache3 = __toESM(require_cache4()); @@ -160751,9 +160757,7 @@ async function run3(actionState) { logger ); const repositoryProperties = repositoryPropertiesResult.orElse({}); - const jobRunUuid = v4_default(); - logger.info(`Job run UUID is ${jobRunUuid}.`); - core21.exportVariable("JOB_RUN_UUID" /* JOB_RUN_UUID */, jobRunUuid); + getJobUUID(logger); core21.exportVariable("CODEQL_ACTION_INIT_HAS_RUN" /* INIT_ACTION_HAS_RUN */, "true"); sourceRoot = path24.resolve( getRequiredEnvParam("GITHUB_WORKSPACE"), @@ -161751,9 +161755,7 @@ async function run6(actionState) { ); const repositoryProperties = repositoryPropertiesResult.orElse({}); const actionStateWithFeatures = { ...actionState, features }; - const jobRunUuid = v4_default(); - logger.info(`Job run UUID is ${jobRunUuid}.`); - core24.exportVariable("JOB_RUN_UUID" /* JOB_RUN_UUID */, jobRunUuid); + getJobUUID(logger); const statusReportBase = await createStatusReportBase( "setup-codeql" /* SetupCodeQL */, "starting", diff --git a/src/init-action.ts b/src/init-action.ts index 4b52ba6ec6..a2ae0918be 100644 --- a/src/init-action.ts +++ b/src/init-action.ts @@ -4,7 +4,6 @@ import * as path from "path"; import * as core from "@actions/core"; import * as io from "@actions/io"; import * as semver from "semver"; -import { v4 as uuidV4 } from "uuid"; import { Action, ActionState, runInActions } from "./action-common"; import { @@ -69,6 +68,7 @@ import { createInitWithConfigStatusReport, createStatusReportBase, getActionsStatus, + getJobUUID, sendStatusReport, } from "./status-report"; import { ToolsDownloadStatusReport } from "./tools-download"; @@ -256,9 +256,7 @@ async function run( const repositoryProperties = repositoryPropertiesResult.orElse({}); // Create a unique identifier for this run. - const jobRunUuid = uuidV4(); - logger.info(`Job run UUID is ${jobRunUuid}.`); - core.exportVariable(EnvVar.JOB_RUN_UUID, jobRunUuid); + getJobUUID(logger); core.exportVariable(EnvVar.INIT_ACTION_HAS_RUN, "true"); diff --git a/src/setup-codeql-action.ts b/src/setup-codeql-action.ts index b2a9e90f36..810931f672 100644 --- a/src/setup-codeql-action.ts +++ b/src/setup-codeql-action.ts @@ -1,5 +1,4 @@ import * as core from "@actions/core"; -import { v4 as uuidV4 } from "uuid"; import { Action, ActionState, runInActions } from "./action-common"; import { @@ -26,6 +25,7 @@ import { InitToolsDownloadFields, createStatusReportBase, getActionsStatus, + getJobUUID, sendStatusReport, } from "./status-report"; import { ToolsDownloadStatusReport } from "./tools-download"; @@ -140,9 +140,8 @@ async function run( const actionStateWithFeatures = { ...actionState, features }; - const jobRunUuid = uuidV4(); - logger.info(`Job run UUID is ${jobRunUuid}.`); - core.exportVariable(EnvVar.JOB_RUN_UUID, jobRunUuid); + // Create a unique identifier for this run. + getJobUUID(logger); const statusReportBase = await createStatusReportBase( ActionName.SetupCodeQL, diff --git a/src/status-report.ts b/src/status-report.ts index d9d2a7ba4c..13cfe8ac39 100644 --- a/src/status-report.ts +++ b/src/status-report.ts @@ -1,6 +1,7 @@ import * as os from "os"; import * as core from "@actions/core"; +import { v4 as uuidV4 } from "uuid"; import { getWorkflowEventName, @@ -59,6 +60,18 @@ export function getDisplayActionName(actionName: ActionName): string { return actionName; } +/** + * Creates a UUIDv4 for the analysis and returns it. + * The generated UUID is also exported as an environment variable. + */ +export function getJobUUID(logger: Logger) { + const jobRunUuid = uuidV4(); + logger.info(`Job run UUID is ${jobRunUuid}.`); + + core.exportVariable(EnvVar.JOB_RUN_UUID, jobRunUuid); + return jobRunUuid; +} + /** * @returns a boolean indicating whether the analysis is considered to be first party. * From c7ae51bb2daea524f6967b00fec6cceaa7b607b5 Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Tue, 28 Jul 2026 18:41:41 +0100 Subject: [PATCH 02/35] Make `ActionState` available and add test --- src/init-action.ts | 2 +- src/setup-codeql-action.ts | 4 ++-- src/status-report.test.ts | 9 +++++++++ src/status-report.ts | 5 +++-- 4 files changed, 15 insertions(+), 5 deletions(-) diff --git a/src/init-action.ts b/src/init-action.ts index a2ae0918be..f1c3916318 100644 --- a/src/init-action.ts +++ b/src/init-action.ts @@ -256,7 +256,7 @@ async function run( const repositoryProperties = repositoryPropertiesResult.orElse({}); // Create a unique identifier for this run. - getJobUUID(logger); + getJobUUID(actionState); core.exportVariable(EnvVar.INIT_ACTION_HAS_RUN, "true"); diff --git a/src/setup-codeql-action.ts b/src/setup-codeql-action.ts index 810931f672..d2f8c6104b 100644 --- a/src/setup-codeql-action.ts +++ b/src/setup-codeql-action.ts @@ -95,7 +95,7 @@ async function sendCompletedStatusReport( /** The main behaviour of this action. */ async function run( - actionState: ActionState<["Base", "Logger", "Actions"]>, + actionState: ActionState<["Base", "Logger", "Env", "Actions"]>, ): Promise { // To capture errors appropriately, keep as much code within the try-catch as // possible, and only use safe functions outside. @@ -141,7 +141,7 @@ async function run( const actionStateWithFeatures = { ...actionState, features }; // Create a unique identifier for this run. - getJobUUID(logger); + getJobUUID(actionState); const statusReportBase = await createStatusReportBase( ActionName.SetupCodeQL, diff --git a/src/status-report.test.ts b/src/status-report.test.ts index 9086dd34ef..0d8fe8108e 100644 --- a/src/status-report.test.ts +++ b/src/status-report.test.ts @@ -1,5 +1,6 @@ import test from "ava"; import * as sinon from "sinon"; +import * as uuid from "uuid"; import * as actionsUtil from "./actions-util"; import { Config } from "./config-utils"; @@ -12,6 +13,7 @@ import { createInitWithConfigStatusReport, createStatusReportBase, getActionsStatus, + getJobUUID, InitStatusReport, InitWithConfigStatusReport, } from "./status-report"; @@ -20,11 +22,18 @@ import { setupActionsVars, createTestConfig, makeMacro, + callee, } from "./testing-utils"; import { BuildMode, ConfigurationError, withTmpDir, wrapError } from "./util"; setupTests(test); +test("getJobUUID - generates valid UUIDs", async (t) => { + await callee(getJobUUID) + .withArgs() + .passes((val) => t.true(uuid.validate(val))); +}); + function setupEnvironmentAndStub(tmpDir: string) { setupActionsVars(tmpDir, tmpDir, { GITHUB_EVENT_NAME: "dynamic", diff --git a/src/status-report.ts b/src/status-report.ts index 13cfe8ac39..08cb05ff93 100644 --- a/src/status-report.ts +++ b/src/status-report.ts @@ -3,6 +3,7 @@ import * as os from "os"; import * as core from "@actions/core"; import { v4 as uuidV4 } from "uuid"; +import type { ActionState } from "./action-common"; import { getWorkflowEventName, getOptionalInput, @@ -64,9 +65,9 @@ export function getDisplayActionName(actionName: ActionName): string { * Creates a UUIDv4 for the analysis and returns it. * The generated UUID is also exported as an environment variable. */ -export function getJobUUID(logger: Logger) { +export function getJobUUID(action: ActionState<["Logger", "ReadOnlyEnv"]>) { const jobRunUuid = uuidV4(); - logger.info(`Job run UUID is ${jobRunUuid}.`); + action.logger.info(`Job run UUID is ${jobRunUuid}.`); core.exportVariable(EnvVar.JOB_RUN_UUID, jobRunUuid); return jobRunUuid; From 049af32c592249a000289bd518b3592923901db3 Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Tue, 28 Jul 2026 18:47:23 +0100 Subject: [PATCH 03/35] Allow `getJobUUID` to retrieve the UUID from the environment --- lib/entry-points.js | 38 ++++++++++++++++++++++++++------------ src/status-report.test.ts | 12 ++++++++++++ src/status-report.ts | 18 ++++++++++++++---- 3 files changed, 52 insertions(+), 16 deletions(-) diff --git a/lib/entry-points.js b/lib/entry-points.js index 287d8a127e..ade2488356 100644 --- a/lib/entry-points.js +++ b/lib/entry-points.js @@ -30477,7 +30477,7 @@ var require_validator = __commonJS({ Validator3.prototype.getSchema = function getSchema(urn) { return this.schemas[urn]; }; - Validator3.prototype.validate = function validate(instance, schema, options, ctx) { + Validator3.prototype.validate = function validate2(instance, schema, options, ctx) { if (typeof schema !== "boolean" && typeof schema !== "object" || schema === null) { throw new SchemaError("Expected `schema` to be an object or boolean"); } @@ -144595,24 +144595,24 @@ function isNumber(value) { function isStringOrUndefined(value) { return value === void 0 || isString(value); } -function defaultCheck(validate) { - return (arg) => ({ unknownKeys: [], invalidKeys: [], valid: validate(arg) }); +function defaultCheck(validate2) { + return (arg) => ({ unknownKeys: [], invalidKeys: [], valid: validate2(arg) }); } -function makeValidator(validate, required = true) { +function makeValidator(validate2, required = true) { return { - validate, - check: defaultCheck(validate), + validate: validate2, + check: defaultCheck(validate2), required }; } var string = makeValidator(isString); var number = makeValidator(isNumber); function array(validator) { - const validate = (val) => { + const validate2 = (val) => { return isArray(val) && val.every((e) => validator.validate(e)); }; return { - validate, + validate: validate2, check: (val, opts, path29) => { const result = successfulCheckSchema(); if (!isArray(val)) { @@ -145728,6 +145728,15 @@ function formatDuration(durationMs) { var os3 = __toESM(require("os")); var core7 = __toESM(require_core()); +// node_modules/uuid/dist-node/regex.js +var regex_default = /^(?:[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$/i; + +// node_modules/uuid/dist-node/validate.js +function validate(uuid) { + return typeof uuid === "string" && regex_default.test(uuid); +} +var validate_default = validate; + // node_modules/uuid/dist-node/stringify.js var byteToHex = []; for (let i = 0; i < 256; ++i) { @@ -146390,9 +146399,14 @@ function getDisplayActionName(actionName) { } return actionName; } -function getJobUUID(logger) { +function getJobUUID(action) { + const existingJobRunUuid = action.env.getOptional("JOB_RUN_UUID" /* JOB_RUN_UUID */); + if (existingJobRunUuid !== void 0 && validate_default(existingJobRunUuid)) { + action.logger.info(`Existing job run UUID is ${existingJobRunUuid}.`); + return existingJobRunUuid; + } const jobRunUuid = v4_default(); - logger.info(`Job run UUID is ${jobRunUuid}.`); + action.logger.info(`Job run UUID is ${jobRunUuid}.`); core7.exportVariable("JOB_RUN_UUID" /* JOB_RUN_UUID */, jobRunUuid); return jobRunUuid; } @@ -160757,7 +160771,7 @@ async function run3(actionState) { logger ); const repositoryProperties = repositoryPropertiesResult.orElse({}); - getJobUUID(logger); + getJobUUID(actionState); core21.exportVariable("CODEQL_ACTION_INIT_HAS_RUN" /* INIT_ACTION_HAS_RUN */, "true"); sourceRoot = path24.resolve( getRequiredEnvParam("GITHUB_WORKSPACE"), @@ -161755,7 +161769,7 @@ async function run6(actionState) { ); const repositoryProperties = repositoryPropertiesResult.orElse({}); const actionStateWithFeatures = { ...actionState, features }; - getJobUUID(logger); + getJobUUID(actionState); const statusReportBase = await createStatusReportBase( "setup-codeql" /* SetupCodeQL */, "starting", diff --git a/src/status-report.test.ts b/src/status-report.test.ts index 0d8fe8108e..6f2c0164b1 100644 --- a/src/status-report.test.ts +++ b/src/status-report.test.ts @@ -31,9 +31,21 @@ setupTests(test); test("getJobUUID - generates valid UUIDs", async (t) => { await callee(getJobUUID) .withArgs() + .logs(t, "Job run UUID is ") .passes((val) => t.true(uuid.validate(val))); }); +test("getJobUUID - retrieves existing job UUIDs", async (t) => { + const existingJobUuid = uuid.v4(); + await callee(getJobUUID) + .withArgs() + .withEnv((env) => { + env.set(EnvVar.JOB_RUN_UUID, existingJobUuid); + }) + .logs(t, `Existing job run UUID is ${existingJobUuid}.`) + .passes(t.deepEqual, existingJobUuid); +}); + function setupEnvironmentAndStub(tmpDir: string) { setupActionsVars(tmpDir, tmpDir, { GITHUB_EVENT_NAME: "dynamic", diff --git a/src/status-report.ts b/src/status-report.ts index 08cb05ff93..ae1e0172ce 100644 --- a/src/status-report.ts +++ b/src/status-report.ts @@ -1,7 +1,7 @@ import * as os from "os"; import * as core from "@actions/core"; -import { v4 as uuidV4 } from "uuid"; +import * as uuid from "uuid"; import type { ActionState } from "./action-common"; import { @@ -62,11 +62,21 @@ export function getDisplayActionName(actionName: ActionName): string { } /** - * Creates a UUIDv4 for the analysis and returns it. - * The generated UUID is also exported as an environment variable. + * Either creates a UUIDv4 for the analysis or retrieves an existing one from the + * environment and returns it. + * If a new UUID is generated, it is also exported as an environment variable. */ export function getJobUUID(action: ActionState<["Logger", "ReadOnlyEnv"]>) { - const jobRunUuid = uuidV4(); + // Check if we already have a UUID for the analysis and return it if so. + const existingJobRunUuid = action.env.getOptional(EnvVar.JOB_RUN_UUID); + + if (existingJobRunUuid !== undefined && uuid.validate(existingJobRunUuid)) { + action.logger.info(`Existing job run UUID is ${existingJobRunUuid}.`); + return existingJobRunUuid; + } + + // Otherwise generate a new UUID. + const jobRunUuid = uuid.v4(); action.logger.info(`Job run UUID is ${jobRunUuid}.`); core.exportVariable(EnvVar.JOB_RUN_UUID, jobRunUuid); From 766928d055114dfca04d7ad722bbc9fe4b928c3e Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Tue, 28 Jul 2026 18:50:56 +0100 Subject: [PATCH 04/35] Call `getJobUUID` in `start-proxy` The `start-proxy` step precedes `init` in Default Setup --- lib/entry-points.js | 5 +++++ src/start-proxy-action.ts | 16 +++++++++++++++- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/lib/entry-points.js b/lib/entry-points.js index ade2488356..6eee65e05d 100644 --- a/lib/entry-points.js +++ b/lib/entry-points.js @@ -162645,6 +162645,11 @@ async function run7(startedAt) { let features; let language; try { + const action = { + logger, + env: new Env(process.env) + }; + getJobUUID(action); persistInputs(); const tempDir = getTemporaryDirectory(); const proxyLogFilePath = path28.resolve(tempDir, "proxy.log"); diff --git a/src/start-proxy-action.ts b/src/start-proxy-action.ts index 3e376ec64f..9da2069df9 100644 --- a/src/start-proxy-action.ts +++ b/src/start-proxy-action.ts @@ -3,8 +3,10 @@ import * as path from "path"; import * as core from "@actions/core"; +import { ActionState } from "./action-common"; import * as actionsUtil from "./actions-util"; import { getGitHubVersion } from "./api-client"; +import { Env } from "./environment"; import { FeatureEnablement, initFeatures } from "./feature-flags"; import { BuiltInLanguage, parseBuiltInLanguage } from "./languages"; import { getActionsLogger, Logger } from "./logging"; @@ -23,7 +25,11 @@ import { import { generateCertificateAuthority } from "./start-proxy/ca"; import { checkProxyEnvironment } from "./start-proxy/environment"; import { checkConnections } from "./start-proxy/reachability"; -import { ActionName, sendUnhandledErrorStatusReport } from "./status-report"; +import { + ActionName, + getJobUUID, + sendUnhandledErrorStatusReport, +} from "./status-report"; import * as util from "./util"; async function run(startedAt: Date) { @@ -35,6 +41,14 @@ async function run(startedAt: Date) { let language: BuiltInLanguage | undefined; try { + const action: ActionState<["Logger", "Env"]> = { + logger, + env: new Env(process.env), + }; + + // Create a unique identifier for this run. + getJobUUID(action); + // Make inputs accessible in the `post` step. actionsUtil.persistInputs(); From e9831f72a27e863fb32ccac5d65261114809b6fd Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Fri, 24 Jul 2026 17:40:26 +0100 Subject: [PATCH 05/35] Add `getRequiredInput` to `ActionsEnv` --- lib/entry-points.js | 2 +- src/actions-util.ts | 3 ++- src/testing-utils.ts | 3 +++ 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/lib/entry-points.js b/lib/entry-points.js index 6eee65e05d..35a0b20922 100644 --- a/lib/entry-points.js +++ b/lib/entry-points.js @@ -145383,7 +145383,7 @@ var Failure = class { // src/actions-util.ts function getActionsEnv() { - return { getOptionalInput }; + return { getRequiredInput, getOptionalInput }; } var getRequiredInput = function(name) { const value = core3.getInput(name); diff --git a/src/actions-util.ts b/src/actions-util.ts index 5fd1ebc4fe..6731f8ef4e 100644 --- a/src/actions-util.ts +++ b/src/actions-util.ts @@ -27,6 +27,7 @@ declare const __CODEQL_ACTION_VERSION__: string; * global functions in tests. */ export interface ActionsEnv { + getRequiredInput: (name: string) => string; getOptionalInput: (name: string) => string | undefined; } @@ -34,7 +35,7 @@ export interface ActionsEnv { * Gets the real `ActionsEnv` used by production code. */ export function getActionsEnv(): ActionsEnv { - return { getOptionalInput }; + return { getRequiredInput, getOptionalInput }; } /** diff --git a/src/testing-utils.ts b/src/testing-utils.ts index 03354653ac..e4fb9adf6f 100644 --- a/src/testing-utils.ts +++ b/src/testing-utils.ts @@ -187,6 +187,9 @@ export function getTestEnv(testEnv: NodeJS.ProcessEnv = {}): Env { */ export function getTestActionsEnv(): ActionsEnv { return { + getRequiredInput: (name) => { + throw new Error(`Input required and not supplied: ${name}`); + }, getOptionalInput: () => undefined, }; } From 60834a0cd9645a12daf4e2e76f667afb0f4cbed2 Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Fri, 24 Jul 2026 22:02:49 +0100 Subject: [PATCH 06/35] Add `exportVariable` to `ActionsEnv` --- lib/entry-points.js | 14 +++++++++----- src/actions-util.ts | 7 ++++++- src/testing-utils.ts | 1 + 3 files changed, 16 insertions(+), 6 deletions(-) diff --git a/lib/entry-points.js b/lib/entry-points.js index 35a0b20922..7ad13ddc55 100644 --- a/lib/entry-points.js +++ b/lib/entry-points.js @@ -21559,7 +21559,7 @@ var require_core = __commonJS({ }; Object.defineProperty(exports2, "__esModule", { value: true }); exports2.platform = exports2.toPlatformPath = exports2.toWin32Path = exports2.toPosixPath = exports2.markdownSummary = exports2.summary = exports2.ExitCode = void 0; - exports2.exportVariable = exportVariable15; + exports2.exportVariable = exportVariable16; exports2.setSecret = setSecret2; exports2.addPath = addPath2; exports2.getInput = getInput2; @@ -21591,7 +21591,7 @@ var require_core = __commonJS({ ExitCode2[ExitCode2["Success"] = 0] = "Success"; ExitCode2[ExitCode2["Failure"] = 1] = "Failure"; })(ExitCode || (exports2.ExitCode = ExitCode = {})); - function exportVariable15(name, val) { + function exportVariable16(name, val) { const convertedVal = (0, utils_1.toCommandValue)(val); process.env[name] = convertedVal; const filePath = process.env["GITHUB_ENV"] || ""; @@ -121026,7 +121026,7 @@ var require_core3 = __commonJS({ ExitCode2[ExitCode2["Success"] = 0] = "Success"; ExitCode2[ExitCode2["Failure"] = 1] = "Failure"; })(ExitCode || (exports2.ExitCode = ExitCode = {})); - function exportVariable15(name, val) { + function exportVariable16(name, val) { const convertedVal = (0, utils_1.toCommandValue)(val); process.env[name] = convertedVal; const filePath = process.env["GITHUB_ENV"] || ""; @@ -121035,7 +121035,7 @@ var require_core3 = __commonJS({ } (0, command_1.issueCommand)("set-env", { name }, convertedVal); } - exports2.exportVariable = exportVariable15; + exports2.exportVariable = exportVariable16; function setSecret2(secret) { (0, command_1.issueCommand)("add-mask", {}, secret); } @@ -145383,7 +145383,11 @@ var Failure = class { // src/actions-util.ts function getActionsEnv() { - return { getRequiredInput, getOptionalInput }; + return { + getRequiredInput, + getOptionalInput, + exportVariable: core3.exportVariable + }; } var getRequiredInput = function(name) { const value = core3.getInput(name); diff --git a/src/actions-util.ts b/src/actions-util.ts index 6731f8ef4e..dd5124620d 100644 --- a/src/actions-util.ts +++ b/src/actions-util.ts @@ -29,13 +29,18 @@ declare const __CODEQL_ACTION_VERSION__: string; export interface ActionsEnv { getRequiredInput: (name: string) => string; getOptionalInput: (name: string) => string | undefined; + exportVariable: (name: string, value: string) => void; } /** * Gets the real `ActionsEnv` used by production code. */ export function getActionsEnv(): ActionsEnv { - return { getRequiredInput, getOptionalInput }; + return { + getRequiredInput, + getOptionalInput, + exportVariable: core.exportVariable, + }; } /** diff --git a/src/testing-utils.ts b/src/testing-utils.ts index e4fb9adf6f..4402458d82 100644 --- a/src/testing-utils.ts +++ b/src/testing-utils.ts @@ -191,6 +191,7 @@ export function getTestActionsEnv(): ActionsEnv { throw new Error(`Input required and not supplied: ${name}`); }, getOptionalInput: () => undefined, + exportVariable: () => {}, }; } From e28cbacfa115612a23d42a9425bfa0aa072443df Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Tue, 28 Jul 2026 19:13:35 +0100 Subject: [PATCH 07/35] Test that `getJobUUID` calls `exportVariable` --- lib/entry-points.js | 5 +++-- src/start-proxy-action.ts | 3 ++- src/status-report.test.ts | 14 +++++++++++++- src/status-report.ts | 6 ++++-- 4 files changed, 22 insertions(+), 6 deletions(-) diff --git a/lib/entry-points.js b/lib/entry-points.js index 7ad13ddc55..0bc32b8bfa 100644 --- a/lib/entry-points.js +++ b/lib/entry-points.js @@ -146411,7 +146411,7 @@ function getJobUUID(action) { } const jobRunUuid = v4_default(); action.logger.info(`Job run UUID is ${jobRunUuid}.`); - core7.exportVariable("JOB_RUN_UUID" /* JOB_RUN_UUID */, jobRunUuid); + action.actions.exportVariable("JOB_RUN_UUID" /* JOB_RUN_UUID */, jobRunUuid); return jobRunUuid; } function isFirstPartyAnalysis(actionName) { @@ -162651,7 +162651,8 @@ async function run7(startedAt) { try { const action = { logger, - env: new Env(process.env) + env: new Env(process.env), + actions: getActionsEnv() }; getJobUUID(action); persistInputs(); diff --git a/src/start-proxy-action.ts b/src/start-proxy-action.ts index 9da2069df9..ee587c04df 100644 --- a/src/start-proxy-action.ts +++ b/src/start-proxy-action.ts @@ -41,9 +41,10 @@ async function run(startedAt: Date) { let language: BuiltInLanguage | undefined; try { - const action: ActionState<["Logger", "Env"]> = { + const action: ActionState<["Logger", "Env", "Actions"]> = { logger, env: new Env(process.env), + actions: actionsUtil.getActionsEnv(), }; // Create a unique identifier for this run. diff --git a/src/status-report.test.ts b/src/status-report.test.ts index 6f2c0164b1..efe272faeb 100644 --- a/src/status-report.test.ts +++ b/src/status-report.test.ts @@ -29,10 +29,22 @@ import { BuildMode, ConfigurationError, withTmpDir, wrapError } from "./util"; setupTests(test); test("getJobUUID - generates valid UUIDs", async (t) => { + const exportVariableStub: sinon.SinonStub<[string, string], void> = + sinon.stub(); + await callee(getJobUUID) .withArgs() + .withActions((env) => { + env.exportVariable = exportVariableStub; + }) .logs(t, "Job run UUID is ") - .passes((val) => t.true(uuid.validate(val))); + .passes((val) => { + t.true(uuid.validate(val)); + + const calls = exportVariableStub.getCalls(); + t.is(calls.length, 1); + t.deepEqual(calls[0].args, [EnvVar.JOB_RUN_UUID, val]); + }); }); test("getJobUUID - retrieves existing job UUIDs", async (t) => { diff --git a/src/status-report.ts b/src/status-report.ts index ae1e0172ce..69cac8a05d 100644 --- a/src/status-report.ts +++ b/src/status-report.ts @@ -66,7 +66,9 @@ export function getDisplayActionName(actionName: ActionName): string { * environment and returns it. * If a new UUID is generated, it is also exported as an environment variable. */ -export function getJobUUID(action: ActionState<["Logger", "ReadOnlyEnv"]>) { +export function getJobUUID( + action: ActionState<["Logger", "ReadOnlyEnv", "Actions"]>, +) { // Check if we already have a UUID for the analysis and return it if so. const existingJobRunUuid = action.env.getOptional(EnvVar.JOB_RUN_UUID); @@ -79,7 +81,7 @@ export function getJobUUID(action: ActionState<["Logger", "ReadOnlyEnv"]>) { const jobRunUuid = uuid.v4(); action.logger.info(`Job run UUID is ${jobRunUuid}.`); - core.exportVariable(EnvVar.JOB_RUN_UUID, jobRunUuid); + action.actions.exportVariable(EnvVar.JOB_RUN_UUID, jobRunUuid); return jobRunUuid; } From 94a12eb6f6fa716ef39d1cd9c61231f08577b870 Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Tue, 28 Jul 2026 19:14:57 +0100 Subject: [PATCH 08/35] Add a test for invalid values --- src/status-report.test.ts | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/status-report.test.ts b/src/status-report.test.ts index efe272faeb..9d0c62efb1 100644 --- a/src/status-report.test.ts +++ b/src/status-report.test.ts @@ -58,6 +58,18 @@ test("getJobUUID - retrieves existing job UUIDs", async (t) => { .passes(t.deepEqual, existingJobUuid); }); +test("getJobUUID - doesn't retrieve invalid UUIDs", async (t) => { + const existingJobUuid = "not-a-uuid"; + await callee(getJobUUID) + .withArgs() + .withEnv((env) => { + env.set(EnvVar.JOB_RUN_UUID, existingJobUuid); + }) + .logs(t, `Job run UUID is `) + .notLogs(t, `Existing job run UUID is ${existingJobUuid}.`) + .passes(t.notDeepEqual, existingJobUuid); +}); + function setupEnvironmentAndStub(tmpDir: string) { setupActionsVars(tmpDir, tmpDir, { GITHUB_EVENT_NAME: "dynamic", From 2e251072b0a905f36699df95f6deabbff6a6ec5a Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Wed, 29 Jul 2026 10:19:14 +0100 Subject: [PATCH 09/35] Use `getEnv()` --- lib/entry-points.js | 2 +- src/start-proxy-action.ts | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/entry-points.js b/lib/entry-points.js index 0bc32b8bfa..0f35b11dfd 100644 --- a/lib/entry-points.js +++ b/lib/entry-points.js @@ -162651,7 +162651,7 @@ async function run7(startedAt) { try { const action = { logger, - env: new Env(process.env), + env: getEnv(), actions: getActionsEnv() }; getJobUUID(action); diff --git a/src/start-proxy-action.ts b/src/start-proxy-action.ts index ee587c04df..67f6d50177 100644 --- a/src/start-proxy-action.ts +++ b/src/start-proxy-action.ts @@ -6,7 +6,6 @@ import * as core from "@actions/core"; import { ActionState } from "./action-common"; import * as actionsUtil from "./actions-util"; import { getGitHubVersion } from "./api-client"; -import { Env } from "./environment"; import { FeatureEnablement, initFeatures } from "./feature-flags"; import { BuiltInLanguage, parseBuiltInLanguage } from "./languages"; import { getActionsLogger, Logger } from "./logging"; @@ -43,7 +42,7 @@ async function run(startedAt: Date) { try { const action: ActionState<["Logger", "Env", "Actions"]> = { logger, - env: new Env(process.env), + env: util.getEnv(), actions: actionsUtil.getActionsEnv(), }; From de57c4a441d83a777b077184c67bfa79f5bd4457 Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Wed, 29 Jul 2026 10:38:08 +0100 Subject: [PATCH 10/35] Move `registry_types` to `StatusReportBase` --- src/start-proxy.ts | 8 +------- src/status-report.ts | 6 ++++++ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/start-proxy.ts b/src/start-proxy.ts index 74e0b498c4..caa1b3054a 100644 --- a/src/start-proxy.ts +++ b/src/start-proxy.ts @@ -83,12 +83,6 @@ export class StartProxyError extends Error { } } -interface StartProxyStatus extends StatusReportBase { - // A comma-separated list of registry types which are configured for CodeQL. - // This only includes registry types we support, not all that are configured. - registry_types: string; -} - /** * Sends a status report for the `start-proxy` action indicating a successful outcome. * @@ -112,7 +106,7 @@ export async function sendSuccessStatusReport( logger, ); if (statusReportBase !== undefined) { - const statusReport: StartProxyStatus = { + const statusReport: StatusReportBase = { ...statusReportBase, registry_types: registry_types.join(","), }; diff --git a/src/status-report.ts b/src/status-report.ts index d9d2a7ba4c..c61bbb828b 100644 --- a/src/status-report.ts +++ b/src/status-report.ts @@ -159,6 +159,12 @@ export interface StatusReportBase { ml_powered_javascript_queries?: string; /** Ref that the workflow was triggered on. */ ref: string; + /** + * A comma-separated list of private registry types which are configured for CodeQL. + * This only includes registry types we support (as determined by the `start-proxy` action), + * not all that are configured. + */ + registry_types?: string; /** Action runner hardware architecture (context runner.arch). */ runner_arch?: string; /** Available disk space on the runner, in bytes. */ From aac07d2a4154cf30c74193cd5c01955a5a0d817e Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Wed, 29 Jul 2026 10:55:46 +0100 Subject: [PATCH 11/35] Include `registry_types` whenever `CODEQL_PROXY_URLS` is set --- lib/entry-points.js | 17 ++++++++++++++ src/status-report.test.ts | 47 ++++++++++++++++++++++++++++++++++++++- src/status-report.ts | 33 ++++++++++++++++++++++++++- 3 files changed, 95 insertions(+), 2 deletions(-) diff --git a/lib/entry-points.js b/lib/entry-points.js index 46c44a8183..24a7eb5c70 100644 --- a/lib/entry-points.js +++ b/lib/entry-points.js @@ -146396,6 +146396,22 @@ function setJobStatusIfUnsuccessful(actionStatus) { ); } } +function getRegistryTypesFromEnv(logger, env = getEnv()) { + const value = env.getOptional("CODEQL_PROXY_URLS" /* PROXY_URLS */); + if (value === void 0) { + return void 0; + } + try { + const data = JSON.parse(value); + const types2 = new Set(data.map((r) => r.type)); + return Array.from(types2).sort().join(","); + } catch (err) { + logger.debug( + `Failed to parse '${"CODEQL_PROXY_URLS" /* PROXY_URLS */}' containing '${value}': ${getErrorMessage(err)}.` + ); + return void 0; + } +} async function createStatusReportBase(actionName, status, actionStartedAt, config, diskInfo, logger, cause, exception) { try { const commitOid = getOptionalInput("sha") || process.env["GITHUB_SHA"] || ""; @@ -146435,6 +146451,7 @@ async function createStatusReportBase(actionName, status, actionStartedAt, confi job_name: jobName, job_run_uuid: jobRunUUID, ref, + registry_types: getRegistryTypesFromEnv(logger), runner_os: runnerOs, started_at: workflowStartedAt, status, diff --git a/src/status-report.test.ts b/src/status-report.test.ts index 9086dd34ef..d8ce1b40b4 100644 --- a/src/status-report.test.ts +++ b/src/status-report.test.ts @@ -3,15 +3,17 @@ import * as sinon from "sinon"; import * as actionsUtil from "./actions-util"; import { Config } from "./config-utils"; -import { EnvVar } from "./environment"; +import { EnvVar, RegistryProxyVars } from "./environment"; import { BuiltInLanguage } from "./languages"; import { getRunnerLogger } from "./logging"; import { ToolsSource } from "./setup-codeql"; +import type { Registry } from "./start-proxy"; import { ActionName, createInitWithConfigStatusReport, createStatusReportBase, getActionsStatus, + getRegistryTypesFromEnv, InitStatusReport, InitWithConfigStatusReport, } from "./status-report"; @@ -20,11 +22,54 @@ import { setupActionsVars, createTestConfig, makeMacro, + getTestEnv, + RecordingLogger, } from "./testing-utils"; import { BuildMode, ConfigurationError, withTmpDir, wrapError } from "./util"; setupTests(test); +test("getRegistryTypesFromEnv - gets unique registry types from environment", async (t) => { + const logger = new RecordingLogger(true); + const env = getTestEnv({ + [RegistryProxyVars.PROXY_URLS]: JSON.stringify([ + { type: "git_source", url: "https://example.com" }, + { type: "git_source", url: "https://github.com" }, + { type: "docker_registry", url: "https://registry.example.com" }, + ] satisfies Array>), + }); + + const result = getRegistryTypesFromEnv(logger, env); + t.deepEqual(result, ["git_source", "docker_registry"].sort().join(",")); +}); + +test("getRegistryTypesFromEnv - returns undefined if the env var is not set", async (t) => { + const logger = new RecordingLogger(true); + const env = getTestEnv({}); + + const result = getRegistryTypesFromEnv(logger, env); + t.is(result, undefined); +}); + +test("getRegistryTypesFromEnv - returns undefined if the env var is not valid JSON", async (t) => { + const logger = new RecordingLogger(true); + const env = getTestEnv({ [RegistryProxyVars.PROXY_URLS]: "[" }); + + const result = getRegistryTypesFromEnv(logger, env); + t.is(result, undefined); +}); + +test("getRegistryTypesFromEnv - returns undefined if the env var is unexpected JSON", async (t) => { + const logger = new RecordingLogger(true); + const env = getTestEnv({ + // Top-level object rather than an array of objects. + [RegistryProxyVars.PROXY_URLS]: JSON.stringify({ type: "git_source" }), + }); + + const result = getRegistryTypesFromEnv(logger, env); + t.is(result, undefined); +}); + function setupEnvironmentAndStub(tmpDir: string) { setupActionsVars(tmpDir, tmpDir, { GITHUB_EVENT_NAME: "dynamic", diff --git a/src/status-report.ts b/src/status-report.ts index c61bbb828b..5778081153 100644 --- a/src/status-report.ts +++ b/src/status-report.ts @@ -17,12 +17,13 @@ import type { ComputedInput, InputName } from "./config/inputs"; import { parseRegistriesWithoutCredentials } from "./config/pack-registries"; import type { DependencyCacheRestoreStatusReport } from "./dependency-caching"; import { DocUrl } from "./doc-url"; -import { EnvVar } from "./environment"; +import { EnvVar, getEnv, ReadOnlyEnv, RegistryProxyVars } from "./environment"; import { getRef } from "./git-utils"; import type { Logger } from "./logging"; import type { OverlayBaseDatabaseDownloadStats } from "./overlay/caching"; import { getRepositoryNwo } from "./repository"; import type { ToolsSource } from "./setup-codeql"; +import type { Registry } from "./start-proxy"; import { ConfigurationError, getRequiredEnvParam, @@ -268,6 +269,35 @@ export interface EventReport { started_at: string; } +/** + * Attempts to retrieve a list of private registry types from the `CODEQL_PROXY_URLS` environment + * variable and returns it as a comma-separated string if successful. Returns `undefined` otherwise. + */ +export function getRegistryTypesFromEnv( + logger: Logger, + env: ReadOnlyEnv = getEnv(), +): string | undefined { + // Try to get the value of the environment variable. + const value = env.getOptional(RegistryProxyVars.PROXY_URLS); + + if (value === undefined) { + return undefined; + } + + // Try to parse the JSON we expect to find in it and return the comma-separated list of + // (unique) registry types. + try { + const data = JSON.parse(value) as Registry[]; + const types = new Set(data.map((r) => r.type)); + return Array.from(types).sort().join(","); + } catch (err) { + logger.debug( + `Failed to parse '${RegistryProxyVars.PROXY_URLS}' containing '${value}': ${getErrorMessage(err)}.`, + ); + return undefined; + } +} + /** * Compose a StatusReport. * @@ -330,6 +360,7 @@ export async function createStatusReportBase( job_name: jobName, job_run_uuid: jobRunUUID, ref, + registry_types: getRegistryTypesFromEnv(logger), runner_os: runnerOs, started_at: workflowStartedAt, status, From eb692f8b49def92b0d25277bd2be0639251c8a81 Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Wed, 29 Jul 2026 10:57:52 +0100 Subject: [PATCH 12/35] Add check to `createStatusReportBase` test --- src/status-report.test.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/status-report.test.ts b/src/status-report.test.ts index d8ce1b40b4..9d3ce0f555 100644 --- a/src/status-report.test.ts +++ b/src/status-report.test.ts @@ -79,6 +79,9 @@ function setupEnvironmentAndStub(tmpDir: string) { process.env[EnvVar.ANALYSIS_KEY] = "analysis-key"; process.env["ImageVersion"] = "2023.05.19.1"; + process.env[RegistryProxyVars.PROXY_URLS] = JSON.stringify([ + { type: "maven_repository" }, + ] satisfies Array>); const getRequiredInput = sinon.stub(actionsUtil, "getRequiredInput"); getRequiredInput.withArgs("matrix").resolves("input/matrix"); @@ -122,6 +125,7 @@ test.serial("createStatusReportBase", async (t) => { t.is(typeof statusReport.job_run_uuid, "string"); t.is(statusReport.languages, "java,swift"); t.is(statusReport.ref, process.env["GITHUB_REF"]!); + t.is(statusReport.registry_types, "maven_repository"); t.is(statusReport.runner_available_disk_space_bytes, 100); t.is(statusReport.runner_image_version, process.env["ImageVersion"]); t.is(statusReport.runner_os, process.env["RUNNER_OS"]!); From e893985e8b57c9f9c845bc3320a4fb540653da70 Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Wed, 29 Jul 2026 11:19:21 +0100 Subject: [PATCH 13/35] Fix `makeValidator` returning `required: boolean` --- lib/entry-points.js | 4 ++-- src/json/index.ts | 7 ++----- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/lib/entry-points.js b/lib/entry-points.js index 24a7eb5c70..a69c9c02fd 100644 --- a/lib/entry-points.js +++ b/lib/entry-points.js @@ -144598,11 +144598,11 @@ function isStringOrUndefined(value) { function defaultCheck(validate) { return (arg) => ({ unknownKeys: [], invalidKeys: [], valid: validate(arg) }); } -function makeValidator(validate, required = true) { +function makeValidator(validate) { return { validate, check: defaultCheck(validate), - required + required: true }; } var string = makeValidator(isString); diff --git a/src/json/index.ts b/src/json/index.ts index 78923f8bac..f040acc932 100644 --- a/src/json/index.ts +++ b/src/json/index.ts @@ -62,14 +62,11 @@ function defaultCheck( return (arg) => ({ unknownKeys: [], invalidKeys: [], valid: validate(arg) }); } -function makeValidator( - validate: (arg: unknown) => arg is T, - required: boolean = true, -) { +function makeValidator(validate: (arg: unknown) => arg is T) { return { validate, check: defaultCheck(validate), - required, + required: true, } as const satisfies Validator; } From 51d51e81216d2a2764c063e5c4ca37c12aa92eb9 Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Wed, 29 Jul 2026 11:19:57 +0100 Subject: [PATCH 14/35] Add `boolean` `Validator` to `json` module --- lib/entry-points.js | 8 ++++++-- src/json/index.ts | 8 ++++++++ 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/lib/entry-points.js b/lib/entry-points.js index a69c9c02fd..302bff49af 100644 --- a/lib/entry-points.js +++ b/lib/entry-points.js @@ -92812,10 +92812,10 @@ var require_util12 = __commonJS({ return objectToString(arg) === "[object Array]"; } exports2.isArray = isArray2; - function isBoolean(arg) { + function isBoolean2(arg) { return typeof arg === "boolean"; } - exports2.isBoolean = isBoolean; + exports2.isBoolean = isBoolean2; function isNull(arg) { return arg === null; } @@ -144592,6 +144592,9 @@ function isString(value) { function isNumber(value) { return typeof value === "number"; } +function isBoolean(value) { + return typeof value === "boolean"; +} function isStringOrUndefined(value) { return value === void 0 || isString(value); } @@ -144607,6 +144610,7 @@ function makeValidator(validate) { } var string = makeValidator(isString); var number = makeValidator(isNumber); +var boolean = makeValidator(isBoolean); function array(validator) { const validate = (val) => { return isArray(val) && val.every((e) => validator.validate(e)); diff --git a/src/json/index.ts b/src/json/index.ts index f040acc932..d3d3abac0c 100644 --- a/src/json/index.ts +++ b/src/json/index.ts @@ -35,6 +35,11 @@ export function isNumber(value: unknown): value is number { return typeof value === "number"; } +/** Asserts that `value` is a boolean. */ +export function isBoolean(value: unknown): value is boolean { + return typeof value === "boolean"; +} + /** Asserts that `value` is either a string or undefined. */ export function isStringOrUndefined( value: unknown, @@ -79,6 +84,9 @@ export const string = makeValidator(isString); /** A validator for number fields in schemas. */ export const number = makeValidator(isNumber); +/** A validator for boolean fields in schemas. */ +export const boolean = makeValidator(isBoolean); + /** A validator for arrays. */ export function array(validator: Validator) { const validate = (val: unknown) => { From e55a57b808525a6830cbf9c336f7ae221169a3a3 Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Wed, 29 Jul 2026 11:21:19 +0100 Subject: [PATCH 15/35] Add `RegistryBase` schema and type --- lib/entry-points.js | 6 ++++++ src/start-proxy/types.ts | 16 +++++++++++----- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/lib/entry-points.js b/lib/entry-points.js index 302bff49af..d52d8169cd 100644 --- a/lib/entry-points.js +++ b/lib/entry-points.js @@ -161989,6 +161989,12 @@ function credentialToStr(credential) { } return result; } +var registryBaseSchema = { + /** The type of the package registry. */ + type: string, + /** Whether the registry replaces the base registry for the ecosystem. */ + "replaces-base": optional(boolean) +}; function getAddressString(address) { if (address.url === void 0) { return address.host; diff --git a/src/start-proxy/types.ts b/src/start-proxy/types.ts index 13369edbfa..17803e9126 100644 --- a/src/start-proxy/types.ts +++ b/src/start-proxy/types.ts @@ -254,13 +254,19 @@ export function credentialToStr(credential: Credential): string { return result; } -/** A package registry is identified by its type and address. */ -export type Registry = { +/** The schema for `RegistryBase` objects. */ +export const registryBaseSchema = { /** The type of the package registry. */ - type: string; + type: json.string, /** Whether the registry replaces the base registry for the ecosystem. */ - "replaces-base"?: boolean; -} & Address; + "replaces-base": json.optional(json.boolean), +} as const satisfies json.Schema; + +/** Information about a registry, other than its address. */ +export type RegistryBase = json.FromSchema; + +/** A package registry is identified by its type and address. */ +export type Registry = RegistryBase & Address; // If a registry has an `url`, then that takes precedence over the `host` which may or may // not be defined. From 13d4882649ba1a2a6abb6c2303df10658daa41f7 Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Wed, 29 Jul 2026 11:35:46 +0100 Subject: [PATCH 16/35] Validate JSON more --- lib/entry-points.js | 316 ++++++++++++++++++++------------------ src/json/index.ts | 17 ++ src/status-report.test.ts | 26 +++- src/status-report.ts | 22 ++- 4 files changed, 222 insertions(+), 159 deletions(-) diff --git a/lib/entry-points.js b/lib/entry-points.js index d52d8169cd..53b7f491af 100644 --- a/lib/entry-points.js +++ b/lib/entry-points.js @@ -96885,7 +96885,7 @@ var require_validators = __commonJS({ throw new ERR_INVALID_ARG_TYPE(name, "a dictionary", value); } }); - var validateArray = hideStackFrames((value, name, minLength = 0) => { + var validateArray2 = hideStackFrames((value, name, minLength = 0) => { if (!ArrayIsArray(value)) { throw new ERR_INVALID_ARG_TYPE(name, "Array", value); } @@ -96895,19 +96895,19 @@ var require_validators = __commonJS({ } }); function validateStringArray(value, name) { - validateArray(value, name); + validateArray2(value, name); for (let i = 0; i < value.length; i++) { validateString(value[i], `${name}[${i}]`); } } function validateBooleanArray(value, name) { - validateArray(value, name); + validateArray2(value, name); for (let i = 0; i < value.length; i++) { validateBoolean(value[i], `${name}[${i}]`); } } function validateAbortSignalArray(value, name) { - validateArray(value, name); + validateArray2(value, name); for (let i = 0; i < value.length; i++) { const signal = value[i]; const indexedName = `${name}[${i}]`; @@ -97003,7 +97003,7 @@ var require_validators = __commonJS({ isInt32, isUint32, parseFileMode, - validateArray, + validateArray: validateArray2, validateStringArray, validateBooleanArray, validateAbortSignalArray, @@ -144692,6 +144692,10 @@ function validateSchema(schema, obj) { const result = checkSchema(schema, obj, { failFast: true }); return result.valid; } +function validateArray(elementSchema, arr) { + const elementValidator = object(elementSchema); + return array(elementValidator).validate(arr); +} function successfulCheckSchema() { return { valid: true, @@ -146343,6 +146347,148 @@ async function getGeneratedFiles(workingDirectory) { return generatedFiles; } +// src/start-proxy/types.ts +var usernameSchema = { + /** The username needed to authenticate to the package registry, if any. */ + username: optionalOrNull(string) +}; +function hasUsername(config) { + return "username" in config; +} +var usernamePasswordSchema = { + /** The password needed to authenticate to the package registry, if any. */ + password: optionalOrNull(string), + ...usernameSchema +}; +function hasUsernameAndPassword(config) { + return hasUsername(config) && "password" in config; +} +var tokenSchema = { + /** The token needed to authenticate to the package registry, if any. */ + token: optionalOrNull(string), + ...usernameSchema +}; +function hasToken(config) { + return "token" in config; +} +function isToken(config) { + return "token" in config && validateSchema(tokenSchema, config); +} +var azureConfigSchema = { + "tenant-id": string, + "client-id": string +}; +function isAzureConfig(config) { + return validateSchema(azureConfigSchema, config); +} +var awsConfigSchema = { + "aws-region": string, + "account-id": string, + "role-name": string, + domain: string, + "domain-owner": string, + audience: optionalOrNull(string) +}; +function isAWSConfig(config) { + return validateSchema(awsConfigSchema, config); +} +var jfrogConfigSchema = { + "jfrog-oidc-provider-name": string, + audience: optionalOrNull(string), + "identity-mapping-name": optionalOrNull(string) +}; +function isJFrogConfig(config) { + return validateSchema(jfrogConfigSchema, config); +} +var cloudsmithConfigSchema = { + namespace: string, + "service-slug": string, + "api-host": string +}; +function isCloudsmithConfig(config) { + return validateSchema(cloudsmithConfigSchema, config); +} +var gcpConfigSchema = { + "workload-identity-provider": string, + "service-account": optionalOrNull(string), + audience: optionalOrNull(string) +}; +function isGCPConfig(config) { + return validateSchema(gcpConfigSchema, config); +} +var oidcSchemas = [ + { schema: azureConfigSchema, name: "Azure" }, + { schema: awsConfigSchema, name: "AWS" }, + { schema: jfrogConfigSchema, name: "JFrog" }, + { schema: cloudsmithConfigSchema, name: "Cloudsmith" }, + { schema: gcpConfigSchema, name: "GCP" } +]; +function credentialToStr(credential) { + let result = `Type: ${credential.type};`; + const appendIfDefined = (name, val) => { + if (isDefined2(val)) { + result += ` ${name}: ${val};`; + } + }; + appendIfDefined("Url", credential.url); + appendIfDefined("Host", credential.host); + if (hasUsername(credential)) { + appendIfDefined("Username", credential.username); + } + if ("password" in credential) { + appendIfDefined( + "Password", + isDefined2(credential.password) ? "***" : void 0 + ); + } + if (hasToken(credential)) { + appendIfDefined("Token", isDefined2(credential.token) ? "***" : void 0); + } + if (isAzureConfig(credential)) { + appendIfDefined("Tenant", credential["tenant-id"]); + appendIfDefined("Client", credential["client-id"]); + } else if (isAWSConfig(credential)) { + appendIfDefined("AWS Region", credential["aws-region"]); + appendIfDefined("AWS Account", credential["account-id"]); + appendIfDefined("AWS Role", credential["role-name"]); + appendIfDefined("AWS Domain", credential.domain); + appendIfDefined("AWS Domain Owner", credential["domain-owner"]); + appendIfDefined("AWS Audience", credential.audience); + } else if (isJFrogConfig(credential)) { + appendIfDefined("JFrog Provider", credential["jfrog-oidc-provider-name"]); + appendIfDefined( + "JFrog Identity Mapping", + credential["identity-mapping-name"] + ); + appendIfDefined("JFrog Audience", credential.audience); + } else if (isCloudsmithConfig(credential)) { + appendIfDefined("Cloudsmith Namespace", credential.namespace); + appendIfDefined("Cloudsmith Service Slug", credential["service-slug"]); + appendIfDefined("Cloudsmith API Host", credential["api-host"]); + } else if (isGCPConfig(credential)) { + appendIfDefined( + "GCP Workload Identity Provider", + credential["workload-identity-provider"] + ); + appendIfDefined("GCP Service Account", credential["service-account"]); + appendIfDefined("GCP Audience", credential.audience); + } + return result; +} +var registryBaseSchema = { + /** The type of the package registry. */ + type: string, + /** Whether the registry replaces the base registry for the ecosystem. */ + "replaces-base": optional(boolean) +}; +function getAddressString(address) { + if (address.url === void 0) { + return address.host; + } else { + return address.url; + } +} + // src/status-report.ts function getDisplayActionName(actionName) { if (actionName === "finish" /* Analyze */) { @@ -146407,11 +146553,23 @@ function getRegistryTypesFromEnv(logger, env = getEnv()) { } try { const data = JSON.parse(value); + if (!isArray(data)) { + logger.debug( + `Expected '${"CODEQL_PROXY_URLS" /* PROXY_URLS */}' to contain a JSON array, but got '${typeof data}'.` + ); + return void 0; + } + if (!validateArray(registryBaseSchema, data)) { + logger.debug( + `Expected '${"CODEQL_PROXY_URLS" /* PROXY_URLS */}' to contain a JSON array of registry objects, but got something else.` + ); + return void 0; + } const types2 = new Set(data.map((r) => r.type)); return Array.from(types2).sort().join(","); } catch (err) { logger.debug( - `Failed to parse '${"CODEQL_PROXY_URLS" /* PROXY_URLS */}' containing '${value}': ${getErrorMessage(err)}.` + `Failed to parse '${"CODEQL_PROXY_URLS" /* PROXY_URLS */}': ${getErrorMessage(err)}.` ); return void 0; } @@ -157400,7 +157558,7 @@ var import_async = __toESM(require_async(), 1); var import_path6 = require("path"); // node_modules/archiver/lib/error.js -var import_util33 = __toESM(require("util"), 1); +var import_util34 = __toESM(require("util"), 1); var ERROR_CODES = { ABORTED: "archive was aborted", DIRECTORYDIRPATHREQUIRED: "diretory dirpath argument must be a non-empty string value", @@ -157425,7 +157583,7 @@ function ArchiverError(code, data) { this.code = code; this.data = data; } -import_util33.default.inherits(ArchiverError, Error); +import_util34.default.inherits(ArchiverError, Error); // node_modules/archiver/lib/core.js var import_readable_stream2 = __toESM(require_ours(), 1); @@ -161861,148 +162019,6 @@ var path26 = __toESM(require("path")); var core26 = __toESM(require_core()); var toolcache4 = __toESM(require_tool_cache()); -// src/start-proxy/types.ts -var usernameSchema = { - /** The username needed to authenticate to the package registry, if any. */ - username: optionalOrNull(string) -}; -function hasUsername(config) { - return "username" in config; -} -var usernamePasswordSchema = { - /** The password needed to authenticate to the package registry, if any. */ - password: optionalOrNull(string), - ...usernameSchema -}; -function hasUsernameAndPassword(config) { - return hasUsername(config) && "password" in config; -} -var tokenSchema = { - /** The token needed to authenticate to the package registry, if any. */ - token: optionalOrNull(string), - ...usernameSchema -}; -function hasToken(config) { - return "token" in config; -} -function isToken(config) { - return "token" in config && validateSchema(tokenSchema, config); -} -var azureConfigSchema = { - "tenant-id": string, - "client-id": string -}; -function isAzureConfig(config) { - return validateSchema(azureConfigSchema, config); -} -var awsConfigSchema = { - "aws-region": string, - "account-id": string, - "role-name": string, - domain: string, - "domain-owner": string, - audience: optionalOrNull(string) -}; -function isAWSConfig(config) { - return validateSchema(awsConfigSchema, config); -} -var jfrogConfigSchema = { - "jfrog-oidc-provider-name": string, - audience: optionalOrNull(string), - "identity-mapping-name": optionalOrNull(string) -}; -function isJFrogConfig(config) { - return validateSchema(jfrogConfigSchema, config); -} -var cloudsmithConfigSchema = { - namespace: string, - "service-slug": string, - "api-host": string -}; -function isCloudsmithConfig(config) { - return validateSchema(cloudsmithConfigSchema, config); -} -var gcpConfigSchema = { - "workload-identity-provider": string, - "service-account": optionalOrNull(string), - audience: optionalOrNull(string) -}; -function isGCPConfig(config) { - return validateSchema(gcpConfigSchema, config); -} -var oidcSchemas = [ - { schema: azureConfigSchema, name: "Azure" }, - { schema: awsConfigSchema, name: "AWS" }, - { schema: jfrogConfigSchema, name: "JFrog" }, - { schema: cloudsmithConfigSchema, name: "Cloudsmith" }, - { schema: gcpConfigSchema, name: "GCP" } -]; -function credentialToStr(credential) { - let result = `Type: ${credential.type};`; - const appendIfDefined = (name, val) => { - if (isDefined2(val)) { - result += ` ${name}: ${val};`; - } - }; - appendIfDefined("Url", credential.url); - appendIfDefined("Host", credential.host); - if (hasUsername(credential)) { - appendIfDefined("Username", credential.username); - } - if ("password" in credential) { - appendIfDefined( - "Password", - isDefined2(credential.password) ? "***" : void 0 - ); - } - if (hasToken(credential)) { - appendIfDefined("Token", isDefined2(credential.token) ? "***" : void 0); - } - if (isAzureConfig(credential)) { - appendIfDefined("Tenant", credential["tenant-id"]); - appendIfDefined("Client", credential["client-id"]); - } else if (isAWSConfig(credential)) { - appendIfDefined("AWS Region", credential["aws-region"]); - appendIfDefined("AWS Account", credential["account-id"]); - appendIfDefined("AWS Role", credential["role-name"]); - appendIfDefined("AWS Domain", credential.domain); - appendIfDefined("AWS Domain Owner", credential["domain-owner"]); - appendIfDefined("AWS Audience", credential.audience); - } else if (isJFrogConfig(credential)) { - appendIfDefined("JFrog Provider", credential["jfrog-oidc-provider-name"]); - appendIfDefined( - "JFrog Identity Mapping", - credential["identity-mapping-name"] - ); - appendIfDefined("JFrog Audience", credential.audience); - } else if (isCloudsmithConfig(credential)) { - appendIfDefined("Cloudsmith Namespace", credential.namespace); - appendIfDefined("Cloudsmith Service Slug", credential["service-slug"]); - appendIfDefined("Cloudsmith API Host", credential["api-host"]); - } else if (isGCPConfig(credential)) { - appendIfDefined( - "GCP Workload Identity Provider", - credential["workload-identity-provider"] - ); - appendIfDefined("GCP Service Account", credential["service-account"]); - appendIfDefined("GCP Audience", credential.audience); - } - return result; -} -var registryBaseSchema = { - /** The type of the package registry. */ - type: string, - /** Whether the registry replaces the base registry for the ecosystem. */ - "replaces-base": optional(boolean) -}; -function getAddressString(address) { - if (address.url === void 0) { - return address.host; - } else { - return address.url; - } -} - // src/start-proxy/validation.ts var core25 = __toESM(require_core()); function cloneCredential(schema, obj) { diff --git a/src/json/index.ts b/src/json/index.ts index d3d3abac0c..d8764ec478 100644 --- a/src/json/index.ts +++ b/src/json/index.ts @@ -226,6 +226,23 @@ export function validateSchema< return result.valid; } +/** + * Validates that `arr` is an array whose elements satisfy at least `elementSchema`. + * Additional keys are accepted in each element. + * + * @param elementSchema The schema to validate the elements against. + * @param arr The array to validate. + * @returns Asserts that `arr` has elements of `schema`'s type if validation is successful. + */ +export function validateArray< + S extends Schema, + T extends UnvalidatedArray = Array>, +>(elementSchema: S, arr: UnvalidatedArray): arr is T { + const elementValidator = object(elementSchema); + + return array(elementValidator).validate(arr); +} + export interface CheckSchemaOptions { /** Whether to stop validation after the first error. */ failFast?: boolean; diff --git a/src/status-report.test.ts b/src/status-report.test.ts index 9d3ce0f555..917a2e4d8e 100644 --- a/src/status-report.test.ts +++ b/src/status-report.test.ts @@ -61,13 +61,27 @@ test("getRegistryTypesFromEnv - returns undefined if the env var is not valid JS test("getRegistryTypesFromEnv - returns undefined if the env var is unexpected JSON", async (t) => { const logger = new RecordingLogger(true); - const env = getTestEnv({ - // Top-level object rather than an array of objects. - [RegistryProxyVars.PROXY_URLS]: JSON.stringify({ type: "git_source" }), - }); - const result = getRegistryTypesFromEnv(logger, env); - t.is(result, undefined); + t.is( + getRegistryTypesFromEnv( + logger, + getTestEnv({ + // Top-level object rather than an array of objects. + [RegistryProxyVars.PROXY_URLS]: JSON.stringify({ type: "git_source" }), + }), + ), + undefined, + ); + t.is( + getRegistryTypesFromEnv( + logger, + getTestEnv({ + // Object has no "type" key. + [RegistryProxyVars.PROXY_URLS]: JSON.stringify([{}]), + }), + ), + undefined, + ); }); function setupEnvironmentAndStub(tmpDir: string) { diff --git a/src/status-report.ts b/src/status-report.ts index 5778081153..a6b263ee08 100644 --- a/src/status-report.ts +++ b/src/status-report.ts @@ -19,11 +19,12 @@ import type { DependencyCacheRestoreStatusReport } from "./dependency-caching"; import { DocUrl } from "./doc-url"; import { EnvVar, getEnv, ReadOnlyEnv, RegistryProxyVars } from "./environment"; import { getRef } from "./git-utils"; +import * as json from "./json"; import type { Logger } from "./logging"; import type { OverlayBaseDatabaseDownloadStats } from "./overlay/caching"; import { getRepositoryNwo } from "./repository"; import type { ToolsSource } from "./setup-codeql"; -import type { Registry } from "./start-proxy"; +import { registryBaseSchema } from "./start-proxy/types"; import { ConfigurationError, getRequiredEnvParam, @@ -287,12 +288,27 @@ export function getRegistryTypesFromEnv( // Try to parse the JSON we expect to find in it and return the comma-separated list of // (unique) registry types. try { - const data = JSON.parse(value) as Registry[]; + const data = JSON.parse(value) as unknown; + + // Check that the parsed JSON meets our expectations. + if (!json.isArray(data)) { + logger.debug( + `Expected '${RegistryProxyVars.PROXY_URLS}' to contain a JSON array, but got '${typeof data}'.`, + ); + return undefined; + } + if (!json.validateArray(registryBaseSchema, data)) { + logger.debug( + `Expected '${RegistryProxyVars.PROXY_URLS}' to contain a JSON array of registry objects, but got something else.`, + ); + return undefined; + } + const types = new Set(data.map((r) => r.type)); return Array.from(types).sort().join(","); } catch (err) { logger.debug( - `Failed to parse '${RegistryProxyVars.PROXY_URLS}' containing '${value}': ${getErrorMessage(err)}.`, + `Failed to parse '${RegistryProxyVars.PROXY_URLS}': ${getErrorMessage(err)}.`, ); return undefined; } From 42a3b947902ef5aef0cda3594c9e0cb60f8f4820 Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Wed, 29 Jul 2026 11:58:44 +0100 Subject: [PATCH 17/35] Add `CODEQL_ACTION_` prefix to `JOB_RUN_UUID` --- .github/workflows/__job-run-uuid-sarif.yml | 4 ++-- lib/entry-points.js | 8 ++++---- pr-checks/checks/job-run-uuid-sarif.yml | 4 ++-- src/environment.ts | 2 +- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/.github/workflows/__job-run-uuid-sarif.yml b/.github/workflows/__job-run-uuid-sarif.yml index cd47fb577e..429a694947 100644 --- a/.github/workflows/__job-run-uuid-sarif.yml +++ b/.github/workflows/__job-run-uuid-sarif.yml @@ -71,8 +71,8 @@ jobs: run: | cd "$RUNNER_TEMP/results" actual=$(jq -r '.runs[0].properties.jobRunUuid' javascript.sarif) - if [[ "$actual" != "$JOB_RUN_UUID" ]]; then - echo "Expected SARIF output to contain job run UUID '$JOB_RUN_UUID', but found '$actual'." + if [[ "$actual" != "$CODEQL_ACTION_JOB_RUN_UUID" ]]; then + echo "Expected SARIF output to contain job run UUID '$CODEQL_ACTION_JOB_RUN_UUID', but found '$actual'." exit 1 else echo "Found job run UUID '$actual'." diff --git a/lib/entry-points.js b/lib/entry-points.js index 0f35b11dfd..a2f6d22c52 100644 --- a/lib/entry-points.js +++ b/lib/entry-points.js @@ -146404,14 +146404,14 @@ function getDisplayActionName(actionName) { return actionName; } function getJobUUID(action) { - const existingJobRunUuid = action.env.getOptional("JOB_RUN_UUID" /* JOB_RUN_UUID */); + const existingJobRunUuid = action.env.getOptional("CODEQL_ACTION_JOB_RUN_UUID" /* JOB_RUN_UUID */); if (existingJobRunUuid !== void 0 && validate_default(existingJobRunUuid)) { action.logger.info(`Existing job run UUID is ${existingJobRunUuid}.`); return existingJobRunUuid; } const jobRunUuid = v4_default(); action.logger.info(`Job run UUID is ${jobRunUuid}.`); - action.actions.exportVariable("JOB_RUN_UUID" /* JOB_RUN_UUID */, jobRunUuid); + action.actions.exportVariable("CODEQL_ACTION_JOB_RUN_UUID" /* JOB_RUN_UUID */, jobRunUuid); return jobRunUuid; } function isFirstPartyAnalysis(actionName) { @@ -146468,7 +146468,7 @@ async function createStatusReportBase(actionName, status, actionStartedAt, confi try { const commitOid = getOptionalInput("sha") || process.env["GITHUB_SHA"] || ""; const ref = await getRef(); - const jobRunUUID = process.env["JOB_RUN_UUID" /* JOB_RUN_UUID */] || ""; + const jobRunUUID = process.env["CODEQL_ACTION_JOB_RUN_UUID" /* JOB_RUN_UUID */] || ""; const workflowRunID = getWorkflowRunID(); const workflowRunAttempt = getWorkflowRunAttempt(); const workflowName = process.env["GITHUB_WORKFLOW"] || ""; @@ -152008,7 +152008,7 @@ function applyAutobuildAzurePipelinesTimeoutFix() { ].join(" "); } async function getJobRunUuidSarifOptions() { - const jobRunUuid = process.env["JOB_RUN_UUID" /* JOB_RUN_UUID */]; + const jobRunUuid = process.env["CODEQL_ACTION_JOB_RUN_UUID" /* JOB_RUN_UUID */]; return jobRunUuid ? [`--sarif-run-property=jobRunUuid=${jobRunUuid}`] : []; } diff --git a/pr-checks/checks/job-run-uuid-sarif.yml b/pr-checks/checks/job-run-uuid-sarif.yml index dc1dd02d43..b86725d944 100644 --- a/pr-checks/checks/job-run-uuid-sarif.yml +++ b/pr-checks/checks/job-run-uuid-sarif.yml @@ -21,8 +21,8 @@ steps: run: | cd "$RUNNER_TEMP/results" actual=$(jq -r '.runs[0].properties.jobRunUuid' javascript.sarif) - if [[ "$actual" != "$JOB_RUN_UUID" ]]; then - echo "Expected SARIF output to contain job run UUID '$JOB_RUN_UUID', but found '$actual'." + if [[ "$actual" != "$CODEQL_ACTION_JOB_RUN_UUID" ]]; then + echo "Expected SARIF output to contain job run UUID '$CODEQL_ACTION_JOB_RUN_UUID', but found '$actual'." exit 1 else echo "Found job run UUID '$actual'." diff --git a/src/environment.ts b/src/environment.ts index 1b00ab7cfb..fea553d602 100644 --- a/src/environment.ts +++ b/src/environment.ts @@ -88,7 +88,7 @@ export enum EnvVar { LOG_VERSION_DEPRECATION = "CODEQL_ACTION_DID_LOG_VERSION_DEPRECATION", /** UUID representing the current job run. */ - JOB_RUN_UUID = "JOB_RUN_UUID", + JOB_RUN_UUID = "CODEQL_ACTION_JOB_RUN_UUID", /** Status for the entire job, submitted to the status report in `init-post` */ JOB_STATUS = "CODEQL_ACTION_JOB_STATUS", From 3ca82bb259b52fe4d0f27055fa58f0fb99694b80 Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Wed, 29 Jul 2026 12:55:16 +0100 Subject: [PATCH 18/35] Change `withActions` to only allow mutations --- src/config/inputs.test.ts | 18 +++++------------- src/testing-utils.ts | 12 +++++------- 2 files changed, 10 insertions(+), 20 deletions(-) diff --git a/src/config/inputs.test.ts b/src/config/inputs.test.ts index a91dc258ea..851dd72e2f 100644 --- a/src/config/inputs.test.ts +++ b/src/config/inputs.test.ts @@ -1,7 +1,7 @@ import test from "ava"; import sinon from "sinon"; -import { getActionsEnv } from "../actions-util"; +import { ActionsEnv } from "../actions-util"; import { Feature } from "../feature-flags"; import { RepositoryPropertyName } from "../feature-flags/properties"; import { callee } from "../testing-utils"; @@ -22,32 +22,26 @@ const expectedRepositoryPropertyResult: ComputedInput = { value: "repo-property-input-value", }; -function stubGetToolsInput() { - const actions = getActionsEnv(); +function stubGetToolsInput(actions: ActionsEnv) { sinon .stub(actions, "getOptionalInput") .withArgs(InputName.Tools) .returns(expectedWorkflowResult.value); - return actions; } const workflowLogMessage = `Using ${InputName.Tools} input from workflow:`; test("getToolsInput - returns workflow input if available", async (t) => { - const actions = stubGetToolsInput(); - await callee(getToolsInput) - .withActions(actions) + .withActions(stubGetToolsInput) .withArgs({}) .logs(t, workflowLogMessage) .passes(t.deepEqual, expectedWorkflowResult); }); test("getToolsInput - returns repository property value if enforced", async (t) => { - const actions = stubGetToolsInput(); - const target = callee(getToolsInput) - .withActions(actions) + .withActions(stubGetToolsInput) .withArgs({ [RepositoryPropertyName.TOOLS]: `!${expectedRepositoryPropertyResult.value}`, }); @@ -65,10 +59,8 @@ test("getToolsInput - returns repository property value if enforced", async (t) }); test("getToolsInput - prefers workflow input", async (t) => { - const actions = stubGetToolsInput(); - const target = callee(getToolsInput) - .withActions(actions) + .withActions(stubGetToolsInput) .withArgs({ [RepositoryPropertyName.TOOLS]: expectedRepositoryPropertyResult.value, }); diff --git a/src/testing-utils.ts b/src/testing-utils.ts index 4402458d82..553a775e93 100644 --- a/src/testing-utils.ts +++ b/src/testing-utils.ts @@ -228,7 +228,8 @@ type DelayedCheck< Fs extends ReadonlyArray, > = (env: Readonly>) => Promise; -export type ValueOrMutation = T | ((val: T) => void); +export type Mutation = (val: T) => void; +export type ValueOrMutation = T | Mutation; /** * Wraps a function that accepts an `ActionState` for testing in different environments. @@ -324,13 +325,10 @@ abstract class BaseEnvBuilder< return result; } - public withActions(arg: ValueOrMutation): this { + /** Applies `fn` to the `ActionsEnv`. */ + public withActions(fn: Mutation): this { const result = this.clone(); - if (typeof arg === "function") { - arg(result.state.actions); - } else { - result.state.actions = arg; - } + fn(result.state.actions); return result; } From 30c33c9286fa4a7c5325301b5a2aa0c5b67a51ec Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Wed, 29 Jul 2026 12:56:51 +0100 Subject: [PATCH 19/35] Make results of function call available to delayed checks --- src/testing-utils.ts | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/testing-utils.ts b/src/testing-utils.ts index 553a775e93..d6fffb69b1 100644 --- a/src/testing-utils.ts +++ b/src/testing-utils.ts @@ -34,11 +34,14 @@ import { ActionName } from "./status-report"; import { DEFAULT_DEBUG_ARTIFACT_NAME, DEFAULT_DEBUG_DATABASE_NAME, + Failure, getEnv, GitHubVariant, GitHubVersion, HTTPError, resetCachedCodeQlVersion, + Result, + Success, } from "./util"; export const SAMPLE_DOTCOM_API_DETAILS = { @@ -226,7 +229,10 @@ type DelayedCheck< Args extends readonly any[], R, Fs extends ReadonlyArray, -> = (env: Readonly>) => Promise; +> = ( + env: Readonly>, + result: Result, ThrownError>, +) => Promise; export type Mutation = (val: T) => void; export type ValueOrMutation = T | Mutation; @@ -441,7 +447,7 @@ class CallableEnvBuilder< // Run other delayed checks. for (const delayedCheck of this.checks) { - await delayedCheck(this); + await delayedCheck(this, new Success(result)); } // Return the results of the function call and the main assertion. @@ -467,7 +473,7 @@ class CallableEnvBuilder< // Run other delayed checks. for (const delayedCheck of this.checks) { - await delayedCheck(this); + await delayedCheck(this, new Failure(error)); } // Return the error. From 36737508ece41f7da5ed9862108928b78f5c24ff Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Wed, 29 Jul 2026 13:00:31 +0100 Subject: [PATCH 20/35] Add `Env`-backed `ActionsEnv` implementation for tests --- src/testing-utils.ts | 66 +++++++++++++++++++++++++++++++------------- 1 file changed, 47 insertions(+), 19 deletions(-) diff --git a/src/testing-utils.ts b/src/testing-utils.ts index d6fffb69b1..94c8435218 100644 --- a/src/testing-utils.ts +++ b/src/testing-utils.ts @@ -185,17 +185,32 @@ export function getTestEnv(testEnv: NodeJS.ProcessEnv = {}): Env { return getEnv(testEnv); } +/** An implementation of `ActionsEnv` for use in tests. */ +class TestActionsEnv implements ActionsEnv { + constructor(private readonly env: Env) {} + + public clone(env: Env): this { + return Object.create(this, { env: { value: env } }) as this; + } + + public getRequiredInput(name: string): string { + throw new Error(`Input required and not supplied: ${name}`); + } + + public getOptionalInput(_name: string): string | undefined { + return undefined; + } + + public exportVariable(name: string, value: string): void { + this.env.set(name, value); + } +} + /** * Gets an `ActionsEnv` instance for use in tests. */ -export function getTestActionsEnv(): ActionsEnv { - return { - getRequiredInput: (name) => { - throw new Error(`Input required and not supplied: ${name}`); - }, - getOptionalInput: () => undefined, - exportVariable: () => {}, - }; +export function getTestActionsEnv(env: Env): TestActionsEnv { + return new TestActionsEnv(env); } /** For testing purposes, we make all available state features accessible in `TestEnv`. */ @@ -213,12 +228,13 @@ type AllState = [ export function initAllState( overrides?: Partial>, ): ActionState { + const env = getTestEnv(); return { name: ActionName.Init, startedAt: new Date(), logger: new RecordingLogger(), - env: getTestEnv(), - actions: getTestActionsEnv(), + env, + actions: getTestActionsEnv(env), apiClient: github.getOctokit("123"), features: createFeatures([]), ...overrides, @@ -247,6 +263,7 @@ abstract class BaseEnvBuilder< > { protected readonly fn: (state: ActionState, ...args: Args) => R; private logger: RecordingLogger; + private actions: TestActionsEnv; protected state: ActionState; protected checks: Array>; @@ -256,15 +273,26 @@ abstract class BaseEnvBuilder< ) { this.fn = fn; this.logger = new RecordingLogger(); - this.state = - cloneFrom !== undefined - ? ({ - ...cloneFrom.state, - env: cloneFrom.state.env.clone(), - actions: Object.create(cloneFrom.state.actions), - logger: this.logger, - } satisfies ActionState) - : initAllState({ logger: this.logger }); + + if (cloneFrom !== undefined) { + const env = cloneFrom.state.env.clone(); + this.actions = cloneFrom.actions.clone(env); + this.state = { + ...cloneFrom.state, + env, + actions: this.actions, + logger: this.logger, + } satisfies ActionState; + } else { + const env = getTestEnv(); + this.actions = getTestActionsEnv(env); + this.state = initAllState({ + logger: this.logger, + env, + actions: this.actions, + }); + } + this.checks = [...(cloneFrom?.checks ?? [])]; } From d2f5cbbe919141b077e54396de7bb0c31da73912 Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Wed, 29 Jul 2026 13:22:46 +0100 Subject: [PATCH 21/35] Add `get` method to `ReadOnlyEnv` --- lib/entry-points.js | 4 ++++ src/environment.ts | 5 +++++ 2 files changed, 9 insertions(+) diff --git a/lib/entry-points.js b/lib/entry-points.js index a2f6d22c52..9a7fbaa8e2 100644 --- a/lib/entry-points.js +++ b/lib/entry-points.js @@ -141565,6 +141565,10 @@ var ReadOnlyEnv = class { clone() { return Object.create(this, { vars: { value: { ...this.vars } } }); } + /** Gets a copy of the underlying environment. */ + get() { + return { ...this.vars }; + } /** Tries to get the value for `name` and throws if there isn't one. */ getRequired(name) { return getRequiredEnvVar(this.vars, name); diff --git a/src/environment.ts b/src/environment.ts index fea553d602..d6ff20391a 100644 --- a/src/environment.ts +++ b/src/environment.ts @@ -270,6 +270,11 @@ export class ReadOnlyEnv { return Object.create(this, { vars: { value: { ...this.vars } } }) as this; } + /** Gets a copy of the underlying environment. */ + public get(): Record { + return { ...this.vars }; + } + /** Tries to get the value for `name` and throws if there isn't one. */ public getRequired(name: string): string { return getRequiredEnvVar(this.vars, name); From 0cebd1d28d761cf2fceb2a3a9ed79dff79ea5a8c Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Wed, 29 Jul 2026 13:24:33 +0100 Subject: [PATCH 22/35] Add `hasEnv` delayed assertion and use for `getJobUUID` test --- src/status-report.test.ts | 15 +++++---------- src/testing-utils.ts | 22 ++++++++++++++++++++++ 2 files changed, 27 insertions(+), 10 deletions(-) diff --git a/src/status-report.test.ts b/src/status-report.test.ts index 9d0c62efb1..17490a60cb 100644 --- a/src/status-report.test.ts +++ b/src/status-report.test.ts @@ -29,21 +29,16 @@ import { BuildMode, ConfigurationError, withTmpDir, wrapError } from "./util"; setupTests(test); test("getJobUUID - generates valid UUIDs", async (t) => { - const exportVariableStub: sinon.SinonStub<[string, string], void> = - sinon.stub(); - await callee(getJobUUID) .withArgs() - .withActions((env) => { - env.exportVariable = exportVariableStub; - }) .logs(t, "Job run UUID is ") + .hasEnv(t, (val) => { + return { + [EnvVar.JOB_RUN_UUID]: val, + }; + }) .passes((val) => { t.true(uuid.validate(val)); - - const calls = exportVariableStub.getCalls(); - t.is(calls.length, 1); - t.deepEqual(calls[0].args, [EnvVar.JOB_RUN_UUID, val]); }); }); diff --git a/src/testing-utils.ts b/src/testing-utils.ts index 94c8435218..279459275d 100644 --- a/src/testing-utils.ts +++ b/src/testing-utils.ts @@ -378,6 +378,28 @@ abstract class BaseEnvBuilder< return result; } + /** + * Adds a delayed check that the environment variables returned by `fn` + * are present in the environment after the main assertion passes. + */ + public hasEnv( + t: ExecutionContext, + fn: ( + value: Awaited | undefined, + error: ThrownError | undefined, + ) => Record, + ): this { + const result = this.clone(); + result.checks.push(async (env, r) => { + const value = r.orElse(undefined); + const error = r.isFailure() ? r.value : undefined; + const expected = fn(value, error); + + t.like(env.getState().env.get(), expected); + }); + return result; + } + /** * Adds a delayed check that `messages` are not logged. The check will be * performed after the main assertion passes. From b411bbcd4ad96437e66f359abc5b628477549c83 Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Wed, 29 Jul 2026 14:27:24 +0100 Subject: [PATCH 23/35] Move `getJobUUID` call into `runInActions` for `init` and `setup-codeql` --- lib/entry-points.js | 8 ++++---- src/action-common.ts | 10 ++++++++-- src/init-action.ts | 4 ---- src/setup-codeql-action.ts | 4 ---- 4 files changed, 12 insertions(+), 14 deletions(-) diff --git a/lib/entry-points.js b/lib/entry-points.js index 9a7fbaa8e2..c56f671098 100644 --- a/lib/entry-points.js +++ b/lib/entry-points.js @@ -146700,13 +146700,15 @@ async function runInActions(action) { const env = getEnv(); const actionsEnv = getActionsEnv(); try { - await action.run({ + const actionState = { name: action.name, startedAt, logger, env, actions: actionsEnv - }); + }; + getJobUUID(actionState); + await action.run(actionState); } catch (error3) { core8.setFailed( `${getDisplayActionName(action.name)} action failed: ${getErrorMessage(error3)}` @@ -160779,7 +160781,6 @@ async function run3(actionState) { logger ); const repositoryProperties = repositoryPropertiesResult.orElse({}); - getJobUUID(actionState); core21.exportVariable("CODEQL_ACTION_INIT_HAS_RUN" /* INIT_ACTION_HAS_RUN */, "true"); sourceRoot = path24.resolve( getRequiredEnvParam("GITHUB_WORKSPACE"), @@ -161777,7 +161778,6 @@ async function run6(actionState) { ); const repositoryProperties = repositoryPropertiesResult.orElse({}); const actionStateWithFeatures = { ...actionState, features }; - getJobUUID(actionState); const statusReportBase = await createStatusReportBase( "setup-codeql" /* SetupCodeQL */, "starting", diff --git a/src/action-common.ts b/src/action-common.ts index cbb1cd3422..be8629addf 100644 --- a/src/action-common.ts +++ b/src/action-common.ts @@ -8,6 +8,7 @@ import { getActionsLogger, Logger } from "./logging"; import { ActionName, getDisplayActionName, + getJobUUID, sendUnhandledErrorStatusReport, } from "./status-report"; import { getEnv, getErrorMessage } from "./util"; @@ -88,13 +89,18 @@ export async function runInActions(action: Action) { const actionsEnv = getActionsEnv(); try { - await action.run({ + const actionState = { name: action.name, startedAt, logger, env, actions: actionsEnv, - }); + }; + + // Create a unique identifier for this run. + getJobUUID(actionState); + + await action.run(actionState); } catch (error) { core.setFailed( `${getDisplayActionName(action.name)} action failed: ${getErrorMessage(error)}`, diff --git a/src/init-action.ts b/src/init-action.ts index f1c3916318..00143df427 100644 --- a/src/init-action.ts +++ b/src/init-action.ts @@ -68,7 +68,6 @@ import { createInitWithConfigStatusReport, createStatusReportBase, getActionsStatus, - getJobUUID, sendStatusReport, } from "./status-report"; import { ToolsDownloadStatusReport } from "./tools-download"; @@ -255,9 +254,6 @@ async function run( ); const repositoryProperties = repositoryPropertiesResult.orElse({}); - // Create a unique identifier for this run. - getJobUUID(actionState); - core.exportVariable(EnvVar.INIT_ACTION_HAS_RUN, "true"); // path.resolve() respects the intended semantics of source-root. If diff --git a/src/setup-codeql-action.ts b/src/setup-codeql-action.ts index d2f8c6104b..7873449f9c 100644 --- a/src/setup-codeql-action.ts +++ b/src/setup-codeql-action.ts @@ -25,7 +25,6 @@ import { InitToolsDownloadFields, createStatusReportBase, getActionsStatus, - getJobUUID, sendStatusReport, } from "./status-report"; import { ToolsDownloadStatusReport } from "./tools-download"; @@ -140,9 +139,6 @@ async function run( const actionStateWithFeatures = { ...actionState, features }; - // Create a unique identifier for this run. - getJobUUID(actionState); - const statusReportBase = await createStatusReportBase( ActionName.SetupCodeQL, "starting", From ba46ff760e2acb42dc881f443ad2bb3cd9de9d28 Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Wed, 29 Jul 2026 14:39:30 +0100 Subject: [PATCH 24/35] Add `transformTelemetryError` option to `Action` --- lib/entry-points.js | 8 +++++++- src/action-common.ts | 20 ++++++++++++++++++-- 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/lib/entry-points.js b/lib/entry-points.js index c56f671098..456ce1be7f 100644 --- a/lib/entry-points.js +++ b/lib/entry-points.js @@ -146713,7 +146713,13 @@ async function runInActions(action) { core8.setFailed( `${getDisplayActionName(action.name)} action failed: ${getErrorMessage(error3)}` ); - await sendUnhandledErrorStatusReport(action.name, startedAt, error3, logger); + const statusReportError = action.transformTelemetryError !== void 0 ? action.transformTelemetryError(wrapError(error3)) : error3; + await sendUnhandledErrorStatusReport( + action.name, + startedAt, + statusReportError, + logger + ); } } diff --git a/src/action-common.ts b/src/action-common.ts index be8629addf..95323e7f2a 100644 --- a/src/action-common.ts +++ b/src/action-common.ts @@ -11,7 +11,7 @@ import { getJobUUID, sendUnhandledErrorStatusReport, } from "./status-report"; -import { getEnv, getErrorMessage } from "./util"; +import { getEnv, getErrorMessage, wrapError } from "./util"; /** Base state that is available to an Action on startup. */ export interface BaseState { @@ -79,6 +79,12 @@ export interface Action { name: ActionName; /** The entry point for the Action. */ run: ActionMain; + /** + * An optional function that transforms a caught error into a message suitable for + * inclusion in a status report. This is primarily intended for the `start-proxy` + * action to replace the thrown `Error`'s message with a safe one. + */ + transformTelemetryError?: (error: Error) => string; } /** A generic entry point that sets up the basic environment for the `action` and runs it. */ @@ -105,6 +111,16 @@ export async function runInActions(action: Action) { core.setFailed( `${getDisplayActionName(action.name)} action failed: ${getErrorMessage(error)}`, ); - await sendUnhandledErrorStatusReport(action.name, startedAt, error, logger); + + const statusReportError = + action.transformTelemetryError !== undefined + ? action.transformTelemetryError(wrapError(error)) + : error; + await sendUnhandledErrorStatusReport( + action.name, + startedAt, + statusReportError, + logger, + ); } } From 8e6fdffc3205654e6d9f7e9a5976eaf55dee895b Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Wed, 29 Jul 2026 14:44:24 +0100 Subject: [PATCH 25/35] Use `runInActions` for `start-proxy` --- lib/entry-points.js | 38 +++++++++++-------------------- src/start-proxy-action.ts | 47 ++++++++++++--------------------------- 2 files changed, 27 insertions(+), 58 deletions(-) diff --git a/lib/entry-points.js b/lib/entry-points.js index 456ce1be7f..66e153b792 100644 --- a/lib/entry-points.js +++ b/lib/entry-points.js @@ -21567,7 +21567,7 @@ var require_core = __commonJS({ exports2.getBooleanInput = getBooleanInput; exports2.setOutput = setOutput7; exports2.setCommandEcho = setCommandEcho; - exports2.setFailed = setFailed13; + exports2.setFailed = setFailed12; exports2.isDebug = isDebug5; exports2.debug = debug6; exports2.error = error3; @@ -21651,7 +21651,7 @@ Support boolean input list: \`true | True | TRUE | false | False | FALSE\``); function setCommandEcho(enabled) { (0, command_1.issue)("echo", enabled ? "on" : "off"); } - function setFailed13(message) { + function setFailed12(message) { process.exitCode = ExitCode.Failure; error3(message); } @@ -121094,11 +121094,11 @@ Support boolean input list: \`true | True | TRUE | false | False | FALSE\``); (0, command_1.issue)("echo", enabled ? "on" : "off"); } exports2.setCommandEcho = setCommandEcho; - function setFailed13(message) { + function setFailed12(message) { process.exitCode = ExitCode.Failure; error3(message); } - exports2.setFailed = setFailed13; + exports2.setFailed = setFailed12; function isDebug5() { return process.env["RUNNER_DEBUG"] === "1"; } @@ -162654,17 +162654,12 @@ async function checkConnections(logger, proxy, backend) { } // src/start-proxy-action.ts -async function run7(startedAt) { - const logger = getActionsLogger(); +async function run7(action) { + const startedAt = action.startedAt; + const logger = action.logger; let features; let language; try { - const action = { - logger, - env: getEnv(), - actions: getActionsEnv() - }; - getJobUUID(action); persistInputs(); const tempDir = getTemporaryDirectory(); const proxyLogFilePath = path28.resolve(tempDir, "proxy.log"); @@ -162727,20 +162722,13 @@ async function run7(startedAt) { await sendFailedStatusReport(logger, startedAt, language, unwrappedError); } } +var startProxyAction = { + name: "start-proxy" /* StartProxy */, + run: run7, + transformTelemetryError: getSafeErrorMessage +}; async function runWrapper8() { - const startedAt = /* @__PURE__ */ new Date(); - const logger = getActionsLogger(); - try { - await run7(startedAt); - } catch (error3) { - core27.setFailed(`start-proxy action failed: ${getErrorMessage(error3)}`); - await sendUnhandledErrorStatusReport( - "start-proxy" /* StartProxy */, - startedAt, - getSafeErrorMessage(wrapError(error3)), - logger - ); - } + await runInActions(startProxyAction); } async function startProxy(binPath, config, logFilePath, logger) { const host = "127.0.0.1"; diff --git a/src/start-proxy-action.ts b/src/start-proxy-action.ts index 67f6d50177..e8b89732f7 100644 --- a/src/start-proxy-action.ts +++ b/src/start-proxy-action.ts @@ -3,12 +3,12 @@ import * as path from "path"; import * as core from "@actions/core"; -import { ActionState } from "./action-common"; +import { Action, ActionState, runInActions } from "./action-common"; import * as actionsUtil from "./actions-util"; import { getGitHubVersion } from "./api-client"; import { FeatureEnablement, initFeatures } from "./feature-flags"; import { BuiltInLanguage, parseBuiltInLanguage } from "./languages"; -import { getActionsLogger, Logger } from "./logging"; +import { Logger } from "./logging"; import { getRepositoryNwo } from "./repository"; import { credentialToStr, @@ -24,31 +24,18 @@ import { import { generateCertificateAuthority } from "./start-proxy/ca"; import { checkProxyEnvironment } from "./start-proxy/environment"; import { checkConnections } from "./start-proxy/reachability"; -import { - ActionName, - getJobUUID, - sendUnhandledErrorStatusReport, -} from "./status-report"; +import { ActionName } from "./status-report"; import * as util from "./util"; -async function run(startedAt: Date) { +async function run(action: ActionState<["Base", "Logger", "Env", "Actions"]>) { // To capture errors appropriately, keep as much code within the try-catch as // possible, and only use safe functions outside. - - const logger = getActionsLogger(); + const startedAt = action.startedAt; + const logger = action.logger; let features: FeatureEnablement | undefined; let language: BuiltInLanguage | undefined; try { - const action: ActionState<["Logger", "Env", "Actions"]> = { - logger, - env: util.getEnv(), - actions: actionsUtil.getActionsEnv(), - }; - - // Create a unique identifier for this run. - getJobUUID(action); - // Make inputs accessible in the `post` step. actionsUtil.persistInputs(); @@ -136,21 +123,15 @@ async function run(startedAt: Date) { } } -export async function runWrapper() { - const startedAt = new Date(); - const logger = getActionsLogger(); +/** Defines the `start-proxy` Action. */ +const startProxyAction: Action = { + name: ActionName.StartProxy, + run, + transformTelemetryError: getSafeErrorMessage, +}; - try { - await run(startedAt); - } catch (error) { - core.setFailed(`start-proxy action failed: ${util.getErrorMessage(error)}`); - await sendUnhandledErrorStatusReport( - ActionName.StartProxy, - startedAt, - getSafeErrorMessage(util.wrapError(error)), - logger, - ); - } +export async function runWrapper() { + await runInActions(startProxyAction); } async function startProxy( From d57c3ffcba10414c396e4bc89f526c3875e18a0d Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Wed, 29 Jul 2026 15:38:27 +0100 Subject: [PATCH 26/35] Add tests for `runInActions` --- src/action-common.test.ts | 123 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 123 insertions(+) create mode 100644 src/action-common.test.ts diff --git a/src/action-common.test.ts b/src/action-common.test.ts new file mode 100644 index 0000000000..fc2e0a9aaa --- /dev/null +++ b/src/action-common.test.ts @@ -0,0 +1,123 @@ +import * as core from "@actions/core"; +import test from "ava"; +import sinon from "sinon"; + +import * as common from "./action-common"; +import * as actionsUtil from "./actions-util"; +import * as environment from "./environment"; +import * as logging from "./logging"; +import { ActionName } from "./status-report"; +import * as statusReport from "./status-report"; +import { + getTestActionsEnv, + getTestEnv, + makeMacro, + RecordingLogger, + setupTests, +} from "./testing-utils"; +import { getErrorMessage } from "./util"; + +setupTests(test); + +interface RunInActionsTestOpts { + runFn?: () => Promise; + expectedErrorMessage?: string; + expectedTelemetryError?: string; +} + +const runInActionsMacro = makeMacro({ + exec: async (t, opts: RunInActionsTestOpts) => { + const expectFailure = opts?.expectedErrorMessage !== undefined; + + const logger = new RecordingLogger(); + const getActionsLogger = sinon + .stub(logging, "getActionsLogger") + .returns(logger); + + const env = getTestEnv(); + const getEnv = sinon.stub(environment, "getEnv").returns(env); + + const actionsEnv = getTestActionsEnv(env); + const getActionsEnv = sinon + .stub(actionsUtil, "getActionsEnv") + .returns(actionsEnv); + + const getJobUUID = sinon + .stub(statusReport, "getJobUUID") + .returns("test-job-uuid"); + + const setFailed = sinon.stub(core, "setFailed"); + const sendUnhandledErrorStatusReport = sinon.stub( + statusReport, + "sendUnhandledErrorStatusReport", + ); + + const name = ActionName.Init; + const run = sinon.stub(); + + if (opts?.runFn) { + run.callsFake(opts.runFn); + } + + const transformTelemetryError = sinon + .stub() + .callsFake((err) => opts?.expectedTelemetryError ?? getErrorMessage(err)); + const testAction: common.Action = { + name, + run, + transformTelemetryError, + }; + + await common.runInActions(testAction); + + // These always should have been called once. + t.true(getActionsLogger.calledOnce); + t.true(getEnv.calledOnce); + t.true(getActionsEnv.calledOnce); + + const expectedActionState = { + actions: actionsEnv, + env, + logger, + name: ActionName.Init, + }; + + t.true(getJobUUID.calledOnceWithExactly(sinon.match(expectedActionState))); + t.true(run.calledOnceWithExactly(sinon.match(expectedActionState))); + + t.is(setFailed.calledOnce, expectFailure ?? false); + t.is(sendUnhandledErrorStatusReport.calledOnce, expectFailure ?? false); + + if (expectFailure) { + t.true( + setFailed.calledOnceWithExactly( + `${statusReport.getDisplayActionName(name)} action failed: ${opts?.expectedErrorMessage}`, + ), + ); + t.true( + sendUnhandledErrorStatusReport.calledOnceWithExactly( + name, + sinon.match.any, + opts?.expectedTelemetryError ?? opts?.expectedErrorMessage, + logger, + ), + ); + } + }, + title: (providedTitle) => `runInActions - ${providedTitle}`, +}); + +runInActionsMacro.serial("calls run", {}); +runInActionsMacro.serial("handles run exceptions", { + runFn: () => { + throw new Error("Test failure"); + }, + expectedErrorMessage: "Test failure", +}); +runInActionsMacro.serial("transforms run exceptions", { + runFn: () => { + throw new Error("Test failure"); + }, + expectedErrorMessage: "Test failure", + expectedTelemetryError: "Transformed failure message", +}); From 8f0a4f23c4e6fd3bdc74965a57db44f356b5ee32 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 29 Jul 2026 17:54:52 +0000 Subject: [PATCH 27/35] Bump the npm-minor group across 1 directory with 2 updates Bumps the npm-minor group with 2 updates in the / directory: [sinon](https://github.com/sinonjs/sinon) and [typescript-eslint](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/typescript-eslint). Updates `sinon` from 22.0.0 to 22.1.0 - [Release notes](https://github.com/sinonjs/sinon/releases) - [Changelog](https://github.com/sinonjs/sinon/blob/main/CHANGES.md) - [Commits](https://github.com/sinonjs/sinon/compare/v22.0.0...v22.1.0) Updates `typescript-eslint` from 8.64.0 to 8.65.0 - [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases) - [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/typescript-eslint/CHANGELOG.md) - [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.65.0/packages/typescript-eslint) --- updated-dependencies: - dependency-name: sinon dependency-version: 22.1.0 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: npm-minor - dependency-name: typescript-eslint dependency-version: 8.65.0 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: npm-minor ... Signed-off-by: dependabot[bot] --- package-lock.json | 148 +++++++++++++++++++++++----------------------- package.json | 4 +- 2 files changed, 76 insertions(+), 76 deletions(-) diff --git a/package-lock.json b/package-lock.json index a01b4a12e7..1e395f75fb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -63,9 +63,9 @@ "glob": "^13.0.6", "globals": "^17.7.0", "nock": "^14.0.16", - "sinon": "^22.0.0", + "sinon": "^22.1.0", "typescript": "^6.0.3", - "typescript-eslint": "^8.64.0" + "typescript-eslint": "^8.65.0" } }, "node_modules/@aashutoshrathi/word-wrap": { @@ -2591,17 +2591,17 @@ "license": "MIT" }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.64.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.64.0.tgz", - "integrity": "sha512-CGvQPBxN3wZLu6Rz2kFUpZeoCm78xUic92ck39KPePkO1NPOwjCqdQnm5Q87tpWw9vcBvW8XLrDXjH9PWYtJ3Q==", + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.65.0.tgz", + "integrity": "sha512-IEgob78X12rHpUmtcwFsXhZdVGJtwTVP8FiCLZkR6GlYVrl2PcuB+KhCE5BlVC/eQpQnu8WXRtkHZuPar+gCRA==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.12.2", - "@typescript-eslint/scope-manager": "8.64.0", - "@typescript-eslint/type-utils": "8.64.0", - "@typescript-eslint/utils": "8.64.0", - "@typescript-eslint/visitor-keys": "8.64.0", + "@typescript-eslint/scope-manager": "8.65.0", + "@typescript-eslint/type-utils": "8.65.0", + "@typescript-eslint/utils": "8.65.0", + "@typescript-eslint/visitor-keys": "8.65.0", "ignore": "^7.0.5", "natural-compare": "^1.4.0", "ts-api-utils": "^2.5.0" @@ -2614,7 +2614,7 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^8.64.0", + "@typescript-eslint/parser": "^8.65.0", "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } @@ -2630,16 +2630,16 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "8.64.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.64.0.tgz", - "integrity": "sha512-KA0OshtlcCCXmbfqyZkM5pV3/WNraJf7DkJRLpyrmwPtud57H5BDX7C3k0LPSPxpprfRL+cJDGabF10mvNCoCw==", + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.65.0.tgz", + "integrity": "sha512-CZ4nMxWwgu1HEEFNkeaCptra9QCtkmKdgf3sWh1rl1trIhmxLilgTV4cwcbQ4wemnT4sWQN8CaKOmdYx+g2gMA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "8.64.0", - "@typescript-eslint/types": "8.64.0", - "@typescript-eslint/typescript-estree": "8.64.0", - "@typescript-eslint/visitor-keys": "8.64.0", + "@typescript-eslint/scope-manager": "8.65.0", + "@typescript-eslint/types": "8.65.0", + "@typescript-eslint/typescript-estree": "8.65.0", + "@typescript-eslint/visitor-keys": "8.65.0", "debug": "^4.4.3" }, "engines": { @@ -2673,14 +2673,14 @@ } }, "node_modules/@typescript-eslint/project-service": { - "version": "8.64.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.64.0.tgz", - "integrity": "sha512-tk4WpOJ6IEbGrVHaNmM0YRrwAD3exZlIK3iadQNAxh4YKk6jvUQ4ecq18n+v7+meh+cJ3j+D8nbk8sRKhlwLQg==", + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.65.0.tgz", + "integrity": "sha512-SxnPhbTsGahizDgbu7oqFH/xVtzIqMd/s+WtnSxNxJZJpLbdT5IPdzg8EZxO3+PoKahXmwJLeNQOpKJb3/bi7Q==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.64.0", - "@typescript-eslint/types": "^8.64.0", + "@typescript-eslint/tsconfig-utils": "^8.65.0", + "@typescript-eslint/types": "^8.65.0", "debug": "^4.4.3" }, "engines": { @@ -2713,14 +2713,14 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.64.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.64.0.tgz", - "integrity": "sha512-CXEaFdYXjSTgKhisNkwCcJwTP8Pl+fmRrEQrri4nm3vU743bALrxzLmq7fHG/7e6a5xO0lDYeURpZmBuhHk54w==", + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.65.0.tgz", + "integrity": "sha512-Esbl8OSYiVxBokYgWPf7VVWg/BE798wXhimnn9ML9Pt5qoDf8bfQlgjlKXR/k98+AcNzlLKYrpCcrcuZ9DZLgg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.64.0", - "@typescript-eslint/visitor-keys": "8.64.0" + "@typescript-eslint/types": "8.65.0", + "@typescript-eslint/visitor-keys": "8.65.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2731,9 +2731,9 @@ } }, "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.64.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.64.0.tgz", - "integrity": "sha512-2yo8rRNKuzbVWQp5kslhANqZ2uDAeROQHBRZNPu8JDsHmeFNj/XJJhX/FhNUWmkHHvoNsKa6+tHJiig87EzsQw==", + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.65.0.tgz", + "integrity": "sha512-j6GzGqCiRdA7Qhur2VVmKZAkBLfnHFQfx4TaJGL9RMveZqCo48jSHHO0DTgizEnGhtWnqmbtCUSrqSkdiY/0Hg==", "dev": true, "license": "MIT", "engines": { @@ -2748,15 +2748,15 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.64.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.64.0.tgz", - "integrity": "sha512-XWG4Fmmv/6SvyS9nH8jWrKs6terwJvE8cyRt1CzYYqzp9OrPhCT4cMc/f7C6RZCwG+qMmiffJS1/qJP8G1URtg==", + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.65.0.tgz", + "integrity": "sha512-YjaZ7PRI5qY7ax2L3PbvX0rRyGtipAReCWs0mhhDBHjH/vl0g0BonaGXrKdKpMbIIsMIwDgbk/xzkBTyAltS5g==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.64.0", - "@typescript-eslint/typescript-estree": "8.64.0", - "@typescript-eslint/utils": "8.64.0", + "@typescript-eslint/types": "8.65.0", + "@typescript-eslint/typescript-estree": "8.65.0", + "@typescript-eslint/utils": "8.65.0", "debug": "^4.4.3", "ts-api-utils": "^2.5.0" }, @@ -2791,9 +2791,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "8.64.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.64.0.tgz", - "integrity": "sha512-qjhfuTfLXjA4IOzXvz0rTjT01BqEiIgPoUeMwiEjnaHKJMTNo8rH5pYW1a2L/0Dnux2fPC85AeyJoWaGa8WxTA==", + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.65.0.tgz", + "integrity": "sha512-JSSwWNy+H0E/01jJEM+hrX6N0OFDzFzeIhHFSAS01tlVaevpG8cFyYRPhS5yjGOvBUx3sqQHVMjCL1CAZZMxBg==", "dev": true, "license": "MIT", "engines": { @@ -2805,16 +2805,16 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.64.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.64.0.tgz", - "integrity": "sha512-Pztpsn1aCE1oWDvDEfUk31nngvvF7vUB5SwHFEaZIFpvw7WJtqUHHL4plBZDA9HfWJJjL13BdG0YrJInTUvoVA==", + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.65.0.tgz", + "integrity": "sha512-JboAE2swaYt4tb1fHhHTABE2K+OLy09XfcTbhnk4Pw96f9dd2e9iYsJ28gBggHlo5z5x1rkyWvcPoTuNTd4oGg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/project-service": "8.64.0", - "@typescript-eslint/tsconfig-utils": "8.64.0", - "@typescript-eslint/types": "8.64.0", - "@typescript-eslint/visitor-keys": "8.64.0", + "@typescript-eslint/project-service": "8.65.0", + "@typescript-eslint/tsconfig-utils": "8.65.0", + "@typescript-eslint/types": "8.65.0", + "@typescript-eslint/visitor-keys": "8.65.0", "debug": "^4.4.3", "minimatch": "^10.2.2", "semver": "^7.7.3", @@ -2843,16 +2843,16 @@ } }, "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { - "version": "5.0.7", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.7.tgz", - "integrity": "sha512-7oFy703dxfY3/NLxC1fh2SUCQ0H9rmAY+5EpDVfXjUTTs+HEwR2nYaqLv+GWcTsumwxPfiz6CzCNkwXwBUwqCA==", + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.8.tgz", + "integrity": "sha512-JZyDyq3D4AUifKTPOB7DELf6XsB3WdPuNxCtob1vFXPsSXhdAiHBWJ/tJ8HAc9aH84BK+5JFZLNkJKx3G9kzQg==", "dev": true, "license": "MIT", "dependencies": { "balanced-match": "^4.0.2" }, "engines": { - "node": "18 || 20 || >=22" + "node": "20 || >=22" } }, "node_modules/@typescript-eslint/typescript-estree/node_modules/debug": { @@ -2874,13 +2874,13 @@ } }, "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { - "version": "10.2.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", - "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", + "version": "10.2.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.6.tgz", + "integrity": "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==", "dev": true, "license": "BlueOak-1.0.0", "dependencies": { - "brace-expansion": "^5.0.5" + "brace-expansion": "^5.0.8" }, "engines": { "node": "18 || 20 || >=22" @@ -2890,16 +2890,16 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.64.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.64.0.tgz", - "integrity": "sha512-aJUGVB3+U0htrrCjoA8qukw8cm8fNCGAxK/tVoS70k8aeb7DETKeFozRiVFIwEeN9WJLsjaP3ph8I60tY2XZoQ==", + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.65.0.tgz", + "integrity": "sha512-gXiwIHsYreboxeJucHKPvgwl7dXt50mF8s1/c00cP/WoVTyWKFdtfhRWwZiXYFU5H2O8vVoSLNrexFZjYS/SGA==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", - "@typescript-eslint/scope-manager": "8.64.0", - "@typescript-eslint/types": "8.64.0", - "@typescript-eslint/typescript-estree": "8.64.0" + "@typescript-eslint/scope-manager": "8.65.0", + "@typescript-eslint/types": "8.65.0", + "@typescript-eslint/typescript-estree": "8.65.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2914,13 +2914,13 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.64.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.64.0.tgz", - "integrity": "sha512-mrtuL8Nsn6gi2H4mo5KMTp823M+3Q19Ew/i+Zlikq20tIMm99C3Ez0dCmkWWnxut20esQvTg8aUSEhMcAOXhEw==", + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.65.0.tgz", + "integrity": "sha512-8C71BQkGjiMmXtop7pHVJu1l2NNShFdkCyD6a2ezzs5vU/L3LRtb69EtcteFwz0mYMPzIgOw0n6OV4VBUWZd7A==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.64.0", + "@typescript-eslint/types": "8.65.0", "eslint-visitor-keys": "^5.0.0" }, "engines": { @@ -8556,9 +8556,9 @@ } }, "node_modules/sinon": { - "version": "22.0.0", - "resolved": "https://registry.npmjs.org/sinon/-/sinon-22.0.0.tgz", - "integrity": "sha512-sq/6DpdXOrLyfbKlXLg/Usc7xu8YXPeLkOFZRvA3bNUSA2lhbrZ06yuXbH1fkzBPCbz9O10+7hznzUsjaYNm0Q==", + "version": "22.1.0", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-22.1.0.tgz", + "integrity": "sha512-n1ajF2rBWMTtEwbKcw4UdFg4nCnDdq/U6RDoxtOd7oapOlRoJ5ynwFx60owROyhDpA9QhMZi0pCO/xtmwFjG7w==", "dev": true, "license": "BSD-3-Clause", "dependencies": { @@ -9320,16 +9320,16 @@ } }, "node_modules/typescript-eslint": { - "version": "8.64.0", - "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.64.0.tgz", - "integrity": "sha512-0qg+pDNMnqYzqH9AnNK+39tejHvsShUOUUoRUgtnTGE7QuMZhiFDnozq8nHJVq+Wae6NMLKNWLg5WmkcC/ndyQ==", + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.65.0.tgz", + "integrity": "sha512-/ggrHAwyjENDusvyxbuqxAC2dTnZg/Z8F+fgQtYIz+L6n/9HfSlEZcFGV/NsMNa6CkGk0xUjUAFwC0vHOflvIA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/eslint-plugin": "8.64.0", - "@typescript-eslint/parser": "8.64.0", - "@typescript-eslint/typescript-estree": "8.64.0", - "@typescript-eslint/utils": "8.64.0" + "@typescript-eslint/eslint-plugin": "8.65.0", + "@typescript-eslint/parser": "8.65.0", + "@typescript-eslint/typescript-estree": "8.65.0", + "@typescript-eslint/utils": "8.65.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" diff --git a/package.json b/package.json index cdd58cd167..ad959a5eee 100644 --- a/package.json +++ b/package.json @@ -71,9 +71,9 @@ "glob": "^13.0.6", "globals": "^17.7.0", "nock": "^14.0.16", - "sinon": "^22.0.0", + "sinon": "^22.1.0", "typescript": "^6.0.3", - "typescript-eslint": "^8.64.0" + "typescript-eslint": "^8.65.0" }, "overrides": { "@actions/tool-cache": { From 3502f795752239ff535bbb8c75134dce966e6700 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 29 Jul 2026 17:57:26 +0000 Subject: [PATCH 28/35] Bump ruby/setup-ruby Bumps the actions-minor group with 1 update in the /.github/workflows directory: [ruby/setup-ruby](https://github.com/ruby/setup-ruby). Updates `ruby/setup-ruby` from 1.319.0 to 1.321.0 - [Release notes](https://github.com/ruby/setup-ruby/releases) - [Changelog](https://github.com/ruby/setup-ruby/blob/master/release.rb) - [Commits](https://github.com/ruby/setup-ruby/compare/003a5c4d8d6321bd302e38f6f0ec593f77f06600...95ef2b042f9d7a56d8268cba8559e2842e2ad01b) --- updated-dependencies: - dependency-name: ruby/setup-ruby dependency-version: 1.321.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: actions-minor ... Signed-off-by: dependabot[bot] --- .github/workflows/__rubocop-multi-language.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/__rubocop-multi-language.yml b/.github/workflows/__rubocop-multi-language.yml index 4809b680ab..c405b44fed 100644 --- a/.github/workflows/__rubocop-multi-language.yml +++ b/.github/workflows/__rubocop-multi-language.yml @@ -54,7 +54,7 @@ jobs: use-all-platform-bundle: 'false' setup-kotlin: 'true' - name: Set up Ruby - uses: ruby/setup-ruby@003a5c4d8d6321bd302e38f6f0ec593f77f06600 # v1.319.0 + uses: ruby/setup-ruby@95ef2b042f9d7a56d8268cba8559e2842e2ad01b # v1.321.0 with: ruby-version: 2.6 - name: Install Code Scanning integration From 60a57910be57f97ad7b63038a43680ac716a4039 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 29 Jul 2026 17:59:24 +0000 Subject: [PATCH 29/35] Rebuild --- pr-checks/checks/rubocop-multi-language.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pr-checks/checks/rubocop-multi-language.yml b/pr-checks/checks/rubocop-multi-language.yml index 7879653f38..37c5d36e90 100644 --- a/pr-checks/checks/rubocop-multi-language.yml +++ b/pr-checks/checks/rubocop-multi-language.yml @@ -5,7 +5,7 @@ versions: - default steps: - name: Set up Ruby - uses: ruby/setup-ruby@003a5c4d8d6321bd302e38f6f0ec593f77f06600 # v1.319.0 + uses: ruby/setup-ruby@95ef2b042f9d7a56d8268cba8559e2842e2ad01b # v1.321.0 with: ruby-version: 2.6 - name: Install Code Scanning integration From 82f035a50156142187b47d8eb748075dbde92426 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 30 Jul 2026 09:21:21 +0000 Subject: [PATCH 30/35] Update changelog and version after v4.37.4 --- CHANGELOG.md | 4 ++++ package-lock.json | 4 ++-- package.json | 2 +- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 51bb95d5c8..65cd1e1fae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ See the [releases page](https://github.com/github/codeql-action/releases) for the relevant changes to the CodeQL CLI and language packs. +## [UNRELEASED] + +No user facing changes. + ## 4.37.4 - 29 Jul 2026 - This version of the CodeQL Action adds support for the `tools` input for the `codeql-action/init` step to be specified using a `github-codeql-tools` [repository property](https://docs.github.com/en/organizations/managing-organization-settings/managing-custom-properties-for-repositories-in-your-organization). This feature will gradually be rolled out following the release of this version. Once rolled out, this allows for the CodeQL CLI version that is used in GitHub-managed workflows, such as Default Setup, to be set to a custom value. For example, customers who run into issues with rate limits when a new CodeQL CLI version is released can set the value to `toolcache` to always use the CodeQL CLI version that is available in the runner toolcache. For Advanced Setup workflows, the value provided for `tools` in the workflow definition always takes precedence unless the value of the repository property starts with `!`. [#4037](https://github.com/github/codeql-action/pull/4037) diff --git a/package-lock.json b/package-lock.json index a01b4a12e7..b72c91e22b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "codeql", - "version": "4.37.4", + "version": "4.37.5", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "codeql", - "version": "4.37.4", + "version": "4.37.5", "license": "MIT", "workspaces": [ "pr-checks" diff --git a/package.json b/package.json index cdd58cd167..8b379f9c9c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "codeql", - "version": "4.37.4", + "version": "4.37.5", "private": true, "description": "CodeQL action", "scripts": { From 06f1d4ffed243918940368743ff3fd9147859de6 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 30 Jul 2026 09:21:35 +0000 Subject: [PATCH 31/35] Rebuild --- lib/entry-points.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/entry-points.js b/lib/entry-points.js index eb81affd67..17f56246ae 100644 --- a/lib/entry-points.js +++ b/lib/entry-points.js @@ -145404,7 +145404,7 @@ function getDiffRangesJsonFilePath(env = getEnv()) { return path2.join(getTemporaryDirectory(env), PR_DIFF_RANGE_JSON_FILENAME); } function getActionVersion() { - return "4.37.4"; + return "4.37.5"; } function getWorkflowEventName(env = getEnv()) { return env.getRequired("GITHUB_EVENT_NAME" /* GITHUB_EVENT_NAME */); From 2d3b351ea6452a9b21346f8d64567e5b833924de Mon Sep 17 00:00:00 2001 From: sim Date: Thu, 30 Jul 2026 18:47:27 +0100 Subject: [PATCH 32/35] Handle network errors when streaming the CodeQL bundle download A network error such as `ECONNRESET` while streaming the download and extraction of the CodeQL bundle terminated the `init` Action rather than falling back to downloading the bundle before extracting it, since no `error` listener was attached to the request returned by `https.get`. Also pipe the response into `tar` using `stream.pipeline` so that errors on the response itself are surfaced and `tar`'s standard input is closed, and abort the request if it stalls. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- CHANGELOG.md | 2 +- lib/entry-points.js | 360 +++++++++++++++++++------------------ src/tar.test.ts | 33 ++++ src/tar.ts | 13 +- src/tools-download.test.ts | 37 ++++ src/tools-download.ts | 28 ++- 6 files changed, 290 insertions(+), 183 deletions(-) create mode 100644 src/tar.test.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 65cd1e1fae..c461878c51 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ See the [releases page](https://github.com/github/codeql-action/releases) for th ## [UNRELEASED] -No user facing changes. +- Fixed a bug where a network error while streaming the download of the CodeQL bundle could terminate the `init` Action instead of falling back to downloading the bundle before extracting it. [#3367](https://github.com/github/codeql-action/issues/3367) ## 4.37.4 - 29 Jul 2026 diff --git a/lib/entry-points.js b/lib/entry-points.js index 8bea6abaaf..a03519a725 100644 --- a/lib/entry-points.js +++ b/lib/entry-points.js @@ -7069,7 +7069,7 @@ var require_client_h2 = __commonJS({ "node_modules/undici/lib/dispatcher/client-h2.js"(exports2, module2) { "use strict"; var assert = require("node:assert"); - var { pipeline } = require("node:stream"); + var { pipeline: pipeline2 } = require("node:stream"); var util3 = require_util(); var { RequestContentLengthMismatchError, @@ -7516,7 +7516,7 @@ var require_client_h2 = __commonJS({ } function writeStream(abort, socket, expectsPayload, h2stream, body, client, request3, contentLength) { assert(contentLength !== 0 || client[kRunning] === 0, "stream body cannot be pipelined"); - const pipe = pipeline( + const pipe = pipeline2( body, h2stream, (err) => { @@ -10506,7 +10506,7 @@ var require_api_pipeline = __commonJS({ util3.destroy(ret, err); } }; - function pipeline(opts, handler2) { + function pipeline2(opts, handler2) { try { const pipelineHandler = new PipelineHandler(opts, handler2); this.dispatch({ ...opts, body: pipelineHandler.req }, pipelineHandler); @@ -10515,7 +10515,7 @@ var require_api_pipeline = __commonJS({ return new PassThrough3().destroy(err); } } - module2.exports = pipeline; + module2.exports = pipeline2; } }); @@ -13680,7 +13680,7 @@ var require_fetch = __commonJS({ subresourceSet } = require_constants3(); var EE = require("node:events"); - var { Readable: Readable3, pipeline, finished } = require("node:stream"); + var { Readable: Readable3, pipeline: pipeline2, finished } = require("node:stream"); var { addAbortListener, isErrored, isReadable, bufferToLowerCasedHeaderName } = require_util(); var { dataURLProcessor, serializeAMimeType, minimizeSupportedMimeType } = require_data_url(); var { getGlobalDispatcher } = require_global2(); @@ -14624,7 +14624,7 @@ var require_fetch = __commonJS({ status, statusText, headersList, - body: decoders.length ? pipeline(this.body, ...decoders, (err) => { + body: decoders.length ? pipeline2(this.body, ...decoders, (err) => { if (err) { this.onError(err); } @@ -18604,7 +18604,7 @@ ${value}`; var require_eventsource = __commonJS({ "node_modules/undici/lib/web/eventsource/eventsource.js"(exports2, module2) { "use strict"; - var { pipeline } = require("node:stream"); + var { pipeline: pipeline2 } = require("node:stream"); var { fetching } = require_fetch(); var { makeRequest } = require_request2(); var { webidl } = require_webidl(); @@ -18762,7 +18762,7 @@ var require_eventsource = __commonJS({ )); } }); - pipeline( + pipeline2( response.body.stream, eventSourceStream, (error3) => { @@ -32788,8 +32788,8 @@ var require_internal_hash_files = __commonJS({ continue; } const hash2 = crypto3.createHash("sha256"); - const pipeline = util3.promisify(stream2.pipeline); - yield pipeline(fs31.createReadStream(file), hash2); + const pipeline2 = util3.promisify(stream2.pipeline); + yield pipeline2(fs31.createReadStream(file), hash2); result.write(hash2.digest()); count++; if (!hasMatch) { @@ -35356,12 +35356,12 @@ var require_pipeline = __commonJS({ } sendRequest(httpClient, request3) { const policies = this.getOrderedPolicies(); - const pipeline = policies.reduceRight((next, policy) => { + const pipeline2 = policies.reduceRight((next, policy) => { return (req) => { return policy.sendRequest(req, next); }; }, (req) => httpClient.sendRequest(req)); - return pipeline(request3); + return pipeline2(request3); } getOrderedPolicies() { if (!this._orderedPolicies) { @@ -38488,26 +38488,26 @@ var require_createPipelineFromOptions = __commonJS({ var tlsPolicy_js_1 = require_tlsPolicy(); var multipartPolicy_js_1 = require_multipartPolicy(); function createPipelineFromOptions(options) { - const pipeline = (0, pipeline_js_1.createEmptyPipeline)(); + const pipeline2 = (0, pipeline_js_1.createEmptyPipeline)(); if (checkEnvironment_js_1.isNodeLike) { if (options.agent) { - pipeline.addPolicy((0, agentPolicy_js_1.agentPolicy)(options.agent)); + pipeline2.addPolicy((0, agentPolicy_js_1.agentPolicy)(options.agent)); } if (options.tlsOptions) { - pipeline.addPolicy((0, tlsPolicy_js_1.tlsPolicy)(options.tlsOptions)); + pipeline2.addPolicy((0, tlsPolicy_js_1.tlsPolicy)(options.tlsOptions)); } - pipeline.addPolicy((0, proxyPolicy_js_1.proxyPolicy)(options.proxyOptions)); - pipeline.addPolicy((0, decompressResponsePolicy_js_1.decompressResponsePolicy)()); + pipeline2.addPolicy((0, proxyPolicy_js_1.proxyPolicy)(options.proxyOptions)); + pipeline2.addPolicy((0, decompressResponsePolicy_js_1.decompressResponsePolicy)()); } - pipeline.addPolicy((0, formDataPolicy_js_1.formDataPolicy)(), { beforePolicies: [multipartPolicy_js_1.multipartPolicyName] }); - pipeline.addPolicy((0, userAgentPolicy_js_1.userAgentPolicy)(options.userAgentOptions)); - pipeline.addPolicy((0, multipartPolicy_js_1.multipartPolicy)(), { afterPhase: "Deserialize" }); - pipeline.addPolicy((0, defaultRetryPolicy_js_1.defaultRetryPolicy)(options.retryOptions), { phase: "Retry" }); + pipeline2.addPolicy((0, formDataPolicy_js_1.formDataPolicy)(), { beforePolicies: [multipartPolicy_js_1.multipartPolicyName] }); + pipeline2.addPolicy((0, userAgentPolicy_js_1.userAgentPolicy)(options.userAgentOptions)); + pipeline2.addPolicy((0, multipartPolicy_js_1.multipartPolicy)(), { afterPhase: "Deserialize" }); + pipeline2.addPolicy((0, defaultRetryPolicy_js_1.defaultRetryPolicy)(options.retryOptions), { phase: "Retry" }); if (checkEnvironment_js_1.isNodeLike) { - pipeline.addPolicy((0, redirectPolicy_js_1.redirectPolicy)(options.redirectOptions), { afterPhase: "Retry" }); + pipeline2.addPolicy((0, redirectPolicy_js_1.redirectPolicy)(options.redirectOptions), { afterPhase: "Retry" }); } - pipeline.addPolicy((0, logPolicy_js_1.logPolicy)(options.loggingOptions), { afterPhase: "Sign" }); - return pipeline; + pipeline2.addPolicy((0, logPolicy_js_1.logPolicy)(options.loggingOptions), { afterPhase: "Sign" }); + return pipeline2; } } }); @@ -38729,21 +38729,21 @@ var require_clientHelpers = __commonJS({ var oauth2AuthenticationPolicy_js_1 = require_oauth2AuthenticationPolicy(); var cachedHttpClient; function createDefaultPipeline(options = {}) { - const pipeline = (0, createPipelineFromOptions_js_1.createPipelineFromOptions)(options); - pipeline.addPolicy((0, apiVersionPolicy_js_1.apiVersionPolicy)(options)); + const pipeline2 = (0, createPipelineFromOptions_js_1.createPipelineFromOptions)(options); + pipeline2.addPolicy((0, apiVersionPolicy_js_1.apiVersionPolicy)(options)); const { credential, authSchemes, allowInsecureConnection } = options; if (credential) { if ((0, credentials_js_1.isApiKeyCredential)(credential)) { - pipeline.addPolicy((0, apiKeyAuthenticationPolicy_js_1.apiKeyAuthenticationPolicy)({ authSchemes, credential, allowInsecureConnection })); + pipeline2.addPolicy((0, apiKeyAuthenticationPolicy_js_1.apiKeyAuthenticationPolicy)({ authSchemes, credential, allowInsecureConnection })); } else if ((0, credentials_js_1.isBasicCredential)(credential)) { - pipeline.addPolicy((0, basicAuthenticationPolicy_js_1.basicAuthenticationPolicy)({ authSchemes, credential, allowInsecureConnection })); + pipeline2.addPolicy((0, basicAuthenticationPolicy_js_1.basicAuthenticationPolicy)({ authSchemes, credential, allowInsecureConnection })); } else if ((0, credentials_js_1.isBearerTokenCredential)(credential)) { - pipeline.addPolicy((0, bearerAuthenticationPolicy_js_1.bearerAuthenticationPolicy)({ authSchemes, credential, allowInsecureConnection })); + pipeline2.addPolicy((0, bearerAuthenticationPolicy_js_1.bearerAuthenticationPolicy)({ authSchemes, credential, allowInsecureConnection })); } else if ((0, credentials_js_1.isOAuth2TokenCredential)(credential)) { - pipeline.addPolicy((0, oauth2AuthenticationPolicy_js_1.oauth2AuthenticationPolicy)({ authSchemes, credential, allowInsecureConnection })); + pipeline2.addPolicy((0, oauth2AuthenticationPolicy_js_1.oauth2AuthenticationPolicy)({ authSchemes, credential, allowInsecureConnection })); } } - return pipeline; + return pipeline2; } function getCachedDefaultHttpsClient() { if (!cachedHttpClient) { @@ -38879,11 +38879,11 @@ var require_sendRequest = __commonJS({ var clientHelpers_js_1 = require_clientHelpers(); var typeGuards_js_1 = require_typeGuards(); var multipart_js_1 = require_multipart(); - async function sendRequest(method, url2, pipeline, options = {}, customHttpClient) { + async function sendRequest(method, url2, pipeline2, options = {}, customHttpClient) { const httpClient = customHttpClient ?? (0, clientHelpers_js_1.getCachedDefaultHttpsClient)(); const request3 = buildPipelineRequest(method, url2, options); try { - const response = await pipeline.sendRequest(httpClient, request3); + const response = await pipeline2.sendRequest(httpClient, request3); const headers = response.headers.toJSON(); const stream2 = response.readableStreamBody ?? response.browserStreamBody; const parsedBody = options.responseAsStream || stream2 !== void 0 ? void 0 : getResponseBody(response); @@ -39146,11 +39146,11 @@ var require_getClient = __commonJS({ var urlHelpers_js_1 = require_urlHelpers(); var checkEnvironment_js_1 = require_checkEnvironment(); function getClient(endpoint2, clientOptions = {}) { - const pipeline = clientOptions.pipeline ?? (0, clientHelpers_js_1.createDefaultPipeline)(clientOptions); + const pipeline2 = clientOptions.pipeline ?? (0, clientHelpers_js_1.createDefaultPipeline)(clientOptions); if (clientOptions.additionalPolicies?.length) { for (const { policy, position } of clientOptions.additionalPolicies) { const afterPhase = position === "perRetry" ? "Sign" : void 0; - pipeline.addPolicy(policy, { + pipeline2.addPolicy(policy, { afterPhase }); } @@ -39161,53 +39161,53 @@ var require_getClient = __commonJS({ const getUrl = (requestOptions) => (0, urlHelpers_js_1.buildRequestUrl)(endpointUrl, path29, args, { allowInsecureConnection, ...requestOptions }); return { get: (requestOptions = {}) => { - return buildOperation("GET", getUrl(requestOptions), pipeline, requestOptions, allowInsecureConnection, httpClient); + return buildOperation("GET", getUrl(requestOptions), pipeline2, requestOptions, allowInsecureConnection, httpClient); }, post: (requestOptions = {}) => { - return buildOperation("POST", getUrl(requestOptions), pipeline, requestOptions, allowInsecureConnection, httpClient); + return buildOperation("POST", getUrl(requestOptions), pipeline2, requestOptions, allowInsecureConnection, httpClient); }, put: (requestOptions = {}) => { - return buildOperation("PUT", getUrl(requestOptions), pipeline, requestOptions, allowInsecureConnection, httpClient); + return buildOperation("PUT", getUrl(requestOptions), pipeline2, requestOptions, allowInsecureConnection, httpClient); }, patch: (requestOptions = {}) => { - return buildOperation("PATCH", getUrl(requestOptions), pipeline, requestOptions, allowInsecureConnection, httpClient); + return buildOperation("PATCH", getUrl(requestOptions), pipeline2, requestOptions, allowInsecureConnection, httpClient); }, delete: (requestOptions = {}) => { - return buildOperation("DELETE", getUrl(requestOptions), pipeline, requestOptions, allowInsecureConnection, httpClient); + return buildOperation("DELETE", getUrl(requestOptions), pipeline2, requestOptions, allowInsecureConnection, httpClient); }, head: (requestOptions = {}) => { - return buildOperation("HEAD", getUrl(requestOptions), pipeline, requestOptions, allowInsecureConnection, httpClient); + return buildOperation("HEAD", getUrl(requestOptions), pipeline2, requestOptions, allowInsecureConnection, httpClient); }, options: (requestOptions = {}) => { - return buildOperation("OPTIONS", getUrl(requestOptions), pipeline, requestOptions, allowInsecureConnection, httpClient); + return buildOperation("OPTIONS", getUrl(requestOptions), pipeline2, requestOptions, allowInsecureConnection, httpClient); }, trace: (requestOptions = {}) => { - return buildOperation("TRACE", getUrl(requestOptions), pipeline, requestOptions, allowInsecureConnection, httpClient); + return buildOperation("TRACE", getUrl(requestOptions), pipeline2, requestOptions, allowInsecureConnection, httpClient); } }; }; return { path: client, pathUnchecked: client, - pipeline + pipeline: pipeline2 }; } - function buildOperation(method, url2, pipeline, options, allowInsecureConnection, httpClient) { + function buildOperation(method, url2, pipeline2, options, allowInsecureConnection, httpClient) { allowInsecureConnection = options.allowInsecureConnection ?? allowInsecureConnection; return { then: function(onFulfilled, onrejected) { - return (0, sendRequest_js_1.sendRequest)(method, url2, pipeline, { ...options, allowInsecureConnection }, httpClient).then(onFulfilled, onrejected); + return (0, sendRequest_js_1.sendRequest)(method, url2, pipeline2, { ...options, allowInsecureConnection }, httpClient).then(onFulfilled, onrejected); }, async asBrowserStream() { if (checkEnvironment_js_1.isNodeLike) { throw new Error("`asBrowserStream` is supported only in the browser environment. Use `asNodeStream` instead to obtain the response body stream. If you require a Web stream of the response in Node, consider using `Readable.toWeb` on the result of `asNodeStream`."); } else { - return (0, sendRequest_js_1.sendRequest)(method, url2, pipeline, { ...options, allowInsecureConnection, responseAsStream: true }, httpClient); + return (0, sendRequest_js_1.sendRequest)(method, url2, pipeline2, { ...options, allowInsecureConnection, responseAsStream: true }, httpClient); } }, async asNodeStream() { if (checkEnvironment_js_1.isNodeLike) { - return (0, sendRequest_js_1.sendRequest)(method, url2, pipeline, { ...options, allowInsecureConnection, responseAsStream: true }, httpClient); + return (0, sendRequest_js_1.sendRequest)(method, url2, pipeline2, { ...options, allowInsecureConnection, responseAsStream: true }, httpClient); } else { throw new Error("`isNodeStream` is not supported in the browser environment. Use `asBrowserStream` to obtain the response body stream."); } @@ -40697,31 +40697,31 @@ var require_createPipelineFromOptions2 = __commonJS({ var tracingPolicy_js_1 = require_tracingPolicy(); var wrapAbortSignalLikePolicy_js_1 = require_wrapAbortSignalLikePolicy(); function createPipelineFromOptions(options) { - const pipeline = (0, pipeline_js_1.createEmptyPipeline)(); + const pipeline2 = (0, pipeline_js_1.createEmptyPipeline)(); if (core_util_1.isNodeLike) { if (options.agent) { - pipeline.addPolicy((0, agentPolicy_js_1.agentPolicy)(options.agent)); + pipeline2.addPolicy((0, agentPolicy_js_1.agentPolicy)(options.agent)); } if (options.tlsOptions) { - pipeline.addPolicy((0, tlsPolicy_js_1.tlsPolicy)(options.tlsOptions)); - } - pipeline.addPolicy((0, proxyPolicy_js_1.proxyPolicy)(options.proxyOptions)); - pipeline.addPolicy((0, decompressResponsePolicy_js_1.decompressResponsePolicy)()); - } - pipeline.addPolicy((0, wrapAbortSignalLikePolicy_js_1.wrapAbortSignalLikePolicy)()); - pipeline.addPolicy((0, formDataPolicy_js_1.formDataPolicy)(), { beforePolicies: [multipartPolicy_js_1.multipartPolicyName] }); - pipeline.addPolicy((0, userAgentPolicy_js_1.userAgentPolicy)(options.userAgentOptions)); - pipeline.addPolicy((0, setClientRequestIdPolicy_js_1.setClientRequestIdPolicy)(options.telemetryOptions?.clientRequestIdHeaderName)); - pipeline.addPolicy((0, multipartPolicy_js_1.multipartPolicy)(), { afterPhase: "Deserialize" }); - pipeline.addPolicy((0, defaultRetryPolicy_js_1.defaultRetryPolicy)(options.retryOptions), { phase: "Retry" }); - pipeline.addPolicy((0, tracingPolicy_js_1.tracingPolicy)({ ...options.userAgentOptions, ...options.loggingOptions }), { + pipeline2.addPolicy((0, tlsPolicy_js_1.tlsPolicy)(options.tlsOptions)); + } + pipeline2.addPolicy((0, proxyPolicy_js_1.proxyPolicy)(options.proxyOptions)); + pipeline2.addPolicy((0, decompressResponsePolicy_js_1.decompressResponsePolicy)()); + } + pipeline2.addPolicy((0, wrapAbortSignalLikePolicy_js_1.wrapAbortSignalLikePolicy)()); + pipeline2.addPolicy((0, formDataPolicy_js_1.formDataPolicy)(), { beforePolicies: [multipartPolicy_js_1.multipartPolicyName] }); + pipeline2.addPolicy((0, userAgentPolicy_js_1.userAgentPolicy)(options.userAgentOptions)); + pipeline2.addPolicy((0, setClientRequestIdPolicy_js_1.setClientRequestIdPolicy)(options.telemetryOptions?.clientRequestIdHeaderName)); + pipeline2.addPolicy((0, multipartPolicy_js_1.multipartPolicy)(), { afterPhase: "Deserialize" }); + pipeline2.addPolicy((0, defaultRetryPolicy_js_1.defaultRetryPolicy)(options.retryOptions), { phase: "Retry" }); + pipeline2.addPolicy((0, tracingPolicy_js_1.tracingPolicy)({ ...options.userAgentOptions, ...options.loggingOptions }), { afterPhase: "Retry" }); if (core_util_1.isNodeLike) { - pipeline.addPolicy((0, redirectPolicy_js_1.redirectPolicy)(options.redirectOptions), { afterPhase: "Retry" }); + pipeline2.addPolicy((0, redirectPolicy_js_1.redirectPolicy)(options.redirectOptions), { afterPhase: "Retry" }); } - pipeline.addPolicy((0, logPolicy_js_1.logPolicy)(options.loggingOptions), { afterPhase: "Sign" }); - return pipeline; + pipeline2.addPolicy((0, logPolicy_js_1.logPolicy)(options.loggingOptions), { afterPhase: "Sign" }); + return pipeline2; } } }); @@ -41635,8 +41635,8 @@ var require_disableKeepAlivePolicy = __commonJS({ } }; } - function pipelineContainsDisableKeepAlivePolicy(pipeline) { - return pipeline.getOrderedPolicies().some((policy) => policy.name === exports2.disableKeepAlivePolicyName); + function pipelineContainsDisableKeepAlivePolicy(pipeline2) { + return pipeline2.getOrderedPolicies().some((policy) => policy.name === exports2.disableKeepAlivePolicyName); } } }); @@ -42975,18 +42975,18 @@ var require_pipeline3 = __commonJS({ var core_rest_pipeline_1 = require_commonjs6(); var serializationPolicy_js_1 = require_serializationPolicy(); function createClientPipeline(options = {}) { - const pipeline = (0, core_rest_pipeline_1.createPipelineFromOptions)(options ?? {}); + const pipeline2 = (0, core_rest_pipeline_1.createPipelineFromOptions)(options ?? {}); if (options.credentialOptions) { - pipeline.addPolicy((0, core_rest_pipeline_1.bearerTokenAuthenticationPolicy)({ + pipeline2.addPolicy((0, core_rest_pipeline_1.bearerTokenAuthenticationPolicy)({ credential: options.credentialOptions.credential, scopes: options.credentialOptions.credentialScopes })); } - pipeline.addPolicy((0, serializationPolicy_js_1.serializationPolicy)(options.serializationOptions), { phase: "Serialize" }); - pipeline.addPolicy((0, deserializationPolicy_js_1.deserializationPolicy)(options.deserializationOptions), { + pipeline2.addPolicy((0, serializationPolicy_js_1.serializationPolicy)(options.serializationOptions), { phase: "Serialize" }); + pipeline2.addPolicy((0, deserializationPolicy_js_1.deserializationPolicy)(options.deserializationOptions), { phase: "Deserialize" }); - return pipeline; + return pipeline2; } } }); @@ -50204,11 +50204,11 @@ var require_Pipeline = __commonJS({ var StorageSharedKeyCredentialPolicyV2_js_1 = require_StorageSharedKeyCredentialPolicyV22(); var StorageBrowserPolicyFactory_js_1 = require_StorageBrowserPolicyFactory2(); var StorageCorrectContentLengthPolicy_js_1 = require_StorageCorrectContentLengthPolicy2(); - function isPipelineLike(pipeline) { - if (!pipeline || typeof pipeline !== "object") { + function isPipelineLike(pipeline2) { + if (!pipeline2 || typeof pipeline2 !== "object") { return false; } - const castPipeline = pipeline; + const castPipeline = pipeline2; return Array.isArray(castPipeline.factories) && typeof castPipeline.options === "object" && typeof castPipeline.toServiceClientOptions === "function"; } var Pipeline = class { @@ -50248,11 +50248,11 @@ var require_Pipeline = __commonJS({ if (!credential) { credential = new AnonymousCredential_js_1.AnonymousCredential(); } - const pipeline = new Pipeline([], pipelineOptions); - pipeline._credential = credential; - return pipeline; + const pipeline2 = new Pipeline([], pipelineOptions); + pipeline2._credential = credential; + return pipeline2; } - function processDownlevelPipeline(pipeline) { + function processDownlevelPipeline(pipeline2) { const knownFactoryFunctions = [ isAnonymousCredential, isStorageSharedKeyCredential, @@ -50262,8 +50262,8 @@ var require_Pipeline = __commonJS({ isStorageTelemetryPolicyFactory, isCoreHttpPolicyFactory ]; - if (pipeline.factories.length) { - const novelFactories = pipeline.factories.filter((factory) => { + if (pipeline2.factories.length) { + const novelFactories = pipeline2.factories.filter((factory) => { return !knownFactoryFunctions.some((knownFactory) => knownFactory(factory)); }); if (novelFactories.length) { @@ -50276,14 +50276,14 @@ var require_Pipeline = __commonJS({ } return void 0; } - function getCoreClientOptions(pipeline) { - const { httpClient: v1Client, ...restOptions } = pipeline.options; - let httpClient = pipeline._coreHttpClient; + function getCoreClientOptions(pipeline2) { + const { httpClient: v1Client, ...restOptions } = pipeline2.options; + let httpClient = pipeline2._coreHttpClient; if (!httpClient) { httpClient = v1Client ? (0, core_http_compat_1.convertHttpClient)(v1Client) : (0, storage_common_1.getCachedDefaultHttpClient)(); - pipeline._coreHttpClient = httpClient; + pipeline2._coreHttpClient = httpClient; } - let corePipeline = pipeline._corePipeline; + let corePipeline = pipeline2._corePipeline; if (!corePipeline) { const packageDetails = `azsdk-js-azure-storage-blob/${constants_js_1.SDK_VERSION}`; const userAgentPrefix = restOptions.userAgentOptions && restOptions.userAgentOptions.userAgentPrefix ? `${restOptions.userAgentOptions.userAgentPrefix} ${packageDetails}` : `${packageDetails}`; @@ -50324,11 +50324,11 @@ var require_Pipeline = __commonJS({ corePipeline.addPolicy((0, StorageRetryPolicyV2_js_1.storageRetryPolicy)(restOptions.retryOptions), { phase: "Retry" }); corePipeline.addPolicy((0, storage_common_1.storageRequestFailureDetailsParserPolicy)()); corePipeline.addPolicy((0, StorageBrowserPolicyV2_js_1.storageBrowserPolicy)()); - const downlevelResults = processDownlevelPipeline(pipeline); + const downlevelResults = processDownlevelPipeline(pipeline2); if (downlevelResults) { corePipeline.addPolicy(downlevelResults.wrappedPolicies, downlevelResults.afterRetry ? { afterPhase: "Retry" } : void 0); } - const credential = getCredentialFromPipeline(pipeline); + const credential = getCredentialFromPipeline(pipeline2); if ((0, core_auth_1.isTokenCredential)(credential)) { corePipeline.addPolicy((0, core_rest_pipeline_1.bearerTokenAuthenticationPolicy)({ credential, @@ -50341,7 +50341,7 @@ var require_Pipeline = __commonJS({ accountKey: credential.accountKey }), { phase: "Sign" }); } - pipeline._corePipeline = corePipeline; + pipeline2._corePipeline = corePipeline; } return { ...restOptions, @@ -50350,12 +50350,12 @@ var require_Pipeline = __commonJS({ pipeline: corePipeline }; } - function getCredentialFromPipeline(pipeline) { - if (pipeline._credential) { - return pipeline._credential; + function getCredentialFromPipeline(pipeline2) { + if (pipeline2._credential) { + return pipeline2._credential; } let credential = new AnonymousCredential_js_1.AnonymousCredential(); - for (const factory of pipeline.factories) { + for (const factory of pipeline2.factories) { if ((0, core_auth_1.isTokenCredential)(factory.credential)) { credential = factory.credential; } else if (isStorageSharedKeyCredential(factory)) { @@ -63880,13 +63880,13 @@ var require_StorageClient = __commonJS({ * @param url - url to resource * @param pipeline - request policy pipeline. */ - constructor(url2, pipeline) { + constructor(url2, pipeline2) { this.url = (0, utils_common_js_1.escapeURLPath)(url2); this.accountName = (0, utils_common_js_1.getAccountNameFromUrl)(url2); - this.pipeline = pipeline; - this.storageClientContext = new StorageContextClient_js_1.StorageContextClient(this.url, (0, Pipeline_js_1.getCoreClientOptions)(pipeline)); + this.pipeline = pipeline2; + this.storageClientContext = new StorageContextClient_js_1.StorageContextClient(this.url, (0, Pipeline_js_1.getCoreClientOptions)(pipeline2)); this.isHttps = (0, utils_common_js_1.iEqual)((0, utils_common_js_1.getURLScheme)(this.url) || "", "https"); - this.credential = (0, Pipeline_js_1.getCredentialFromPipeline)(pipeline); + this.credential = (0, Pipeline_js_1.getCredentialFromPipeline)(pipeline2); const storageClientContext = this.storageClientContext; storageClientContext.requestContentType = void 0; } @@ -68669,21 +68669,21 @@ var require_Clients = __commonJS({ } constructor(urlOrConnectionString, credentialOrPipelineOrContainerName, blobNameOrOptions, options) { options = options || {}; - let pipeline; + let pipeline2; let url2; if ((0, Pipeline_js_1.isPipelineLike)(credentialOrPipelineOrContainerName)) { url2 = urlOrConnectionString; - pipeline = credentialOrPipelineOrContainerName; + pipeline2 = credentialOrPipelineOrContainerName; } else if (core_util_1.isNodeLike && credentialOrPipelineOrContainerName instanceof StorageSharedKeyCredential_js_1.StorageSharedKeyCredential || credentialOrPipelineOrContainerName instanceof AnonymousCredential_js_1.AnonymousCredential || (0, core_auth_1.isTokenCredential)(credentialOrPipelineOrContainerName)) { url2 = urlOrConnectionString; options = blobNameOrOptions; - pipeline = (0, Pipeline_js_1.newPipeline)(credentialOrPipelineOrContainerName, options); + pipeline2 = (0, Pipeline_js_1.newPipeline)(credentialOrPipelineOrContainerName, options); } else if (!credentialOrPipelineOrContainerName && typeof credentialOrPipelineOrContainerName !== "string") { url2 = urlOrConnectionString; if (blobNameOrOptions && typeof blobNameOrOptions !== "string") { options = blobNameOrOptions; } - pipeline = (0, Pipeline_js_1.newPipeline)(new AnonymousCredential_js_1.AnonymousCredential(), options); + pipeline2 = (0, Pipeline_js_1.newPipeline)(new AnonymousCredential_js_1.AnonymousCredential(), options); } else if (credentialOrPipelineOrContainerName && typeof credentialOrPipelineOrContainerName === "string" && blobNameOrOptions && typeof blobNameOrOptions === "string") { const containerName = credentialOrPipelineOrContainerName; const blobName = blobNameOrOptions; @@ -68695,20 +68695,20 @@ var require_Clients = __commonJS({ if (!options.proxyOptions) { options.proxyOptions = (0, core_rest_pipeline_1.getDefaultProxySettings)(extractedCreds.proxyUri); } - pipeline = (0, Pipeline_js_1.newPipeline)(sharedKeyCredential, options); + pipeline2 = (0, Pipeline_js_1.newPipeline)(sharedKeyCredential, options); } else { throw new Error("Account connection string is only supported in Node.js environment"); } } else if (extractedCreds.kind === "SASConnString") { url2 = (0, utils_common_js_1.appendToURLPath)((0, utils_common_js_1.appendToURLPath)(extractedCreds.url, encodeURIComponent(containerName)), encodeURIComponent(blobName)) + "?" + extractedCreds.accountSas; - pipeline = (0, Pipeline_js_1.newPipeline)(new AnonymousCredential_js_1.AnonymousCredential(), options); + pipeline2 = (0, Pipeline_js_1.newPipeline)(new AnonymousCredential_js_1.AnonymousCredential(), options); } else { throw new Error("Connection string must be either an Account connection string or a SAS connection string"); } } else { throw new Error("Expecting non-empty strings for containerName and blobName parameters"); } - super(url2, pipeline); + super(url2, pipeline2); ({ blobName: this._name, containerName: this._containerName } = this.getBlobAndContainerNamesFromUrl()); this.blobContext = this.storageClientContext.blob; this._snapshot = (0, utils_common_js_1.getURLParameter)(this.url, constants_js_1.URLConstants.Parameters.SNAPSHOT); @@ -69694,19 +69694,19 @@ var require_Clients = __commonJS({ */ appendBlobContext; constructor(urlOrConnectionString, credentialOrPipelineOrContainerName, blobNameOrOptions, options) { - let pipeline; + let pipeline2; let url2; options = options || {}; if ((0, Pipeline_js_1.isPipelineLike)(credentialOrPipelineOrContainerName)) { url2 = urlOrConnectionString; - pipeline = credentialOrPipelineOrContainerName; + pipeline2 = credentialOrPipelineOrContainerName; } else if (core_util_1.isNodeLike && credentialOrPipelineOrContainerName instanceof StorageSharedKeyCredential_js_1.StorageSharedKeyCredential || credentialOrPipelineOrContainerName instanceof AnonymousCredential_js_1.AnonymousCredential || (0, core_auth_1.isTokenCredential)(credentialOrPipelineOrContainerName)) { url2 = urlOrConnectionString; options = blobNameOrOptions; - pipeline = (0, Pipeline_js_1.newPipeline)(credentialOrPipelineOrContainerName, options); + pipeline2 = (0, Pipeline_js_1.newPipeline)(credentialOrPipelineOrContainerName, options); } else if (!credentialOrPipelineOrContainerName && typeof credentialOrPipelineOrContainerName !== "string") { url2 = urlOrConnectionString; - pipeline = (0, Pipeline_js_1.newPipeline)(new AnonymousCredential_js_1.AnonymousCredential(), options); + pipeline2 = (0, Pipeline_js_1.newPipeline)(new AnonymousCredential_js_1.AnonymousCredential(), options); } else if (credentialOrPipelineOrContainerName && typeof credentialOrPipelineOrContainerName === "string" && blobNameOrOptions && typeof blobNameOrOptions === "string") { const containerName = credentialOrPipelineOrContainerName; const blobName = blobNameOrOptions; @@ -69718,20 +69718,20 @@ var require_Clients = __commonJS({ if (!options.proxyOptions) { options.proxyOptions = (0, core_rest_pipeline_1.getDefaultProxySettings)(extractedCreds.proxyUri); } - pipeline = (0, Pipeline_js_1.newPipeline)(sharedKeyCredential, options); + pipeline2 = (0, Pipeline_js_1.newPipeline)(sharedKeyCredential, options); } else { throw new Error("Account connection string is only supported in Node.js environment"); } } else if (extractedCreds.kind === "SASConnString") { url2 = (0, utils_common_js_1.appendToURLPath)((0, utils_common_js_1.appendToURLPath)(extractedCreds.url, encodeURIComponent(containerName)), encodeURIComponent(blobName)) + "?" + extractedCreds.accountSas; - pipeline = (0, Pipeline_js_1.newPipeline)(new AnonymousCredential_js_1.AnonymousCredential(), options); + pipeline2 = (0, Pipeline_js_1.newPipeline)(new AnonymousCredential_js_1.AnonymousCredential(), options); } else { throw new Error("Connection string must be either an Account connection string or a SAS connection string"); } } else { throw new Error("Expecting non-empty strings for containerName and blobName parameters"); } - super(url2, pipeline); + super(url2, pipeline2); this.appendBlobContext = this.storageClientContext.appendBlob; } /** @@ -69967,22 +69967,22 @@ var require_Clients = __commonJS({ */ blockBlobContext; constructor(urlOrConnectionString, credentialOrPipelineOrContainerName, blobNameOrOptions, options) { - let pipeline; + let pipeline2; let url2; options = options || {}; if ((0, Pipeline_js_1.isPipelineLike)(credentialOrPipelineOrContainerName)) { url2 = urlOrConnectionString; - pipeline = credentialOrPipelineOrContainerName; + pipeline2 = credentialOrPipelineOrContainerName; } else if (core_util_1.isNodeLike && credentialOrPipelineOrContainerName instanceof StorageSharedKeyCredential_js_1.StorageSharedKeyCredential || credentialOrPipelineOrContainerName instanceof AnonymousCredential_js_1.AnonymousCredential || (0, core_auth_1.isTokenCredential)(credentialOrPipelineOrContainerName)) { url2 = urlOrConnectionString; options = blobNameOrOptions; - pipeline = (0, Pipeline_js_1.newPipeline)(credentialOrPipelineOrContainerName, options); + pipeline2 = (0, Pipeline_js_1.newPipeline)(credentialOrPipelineOrContainerName, options); } else if (!credentialOrPipelineOrContainerName && typeof credentialOrPipelineOrContainerName !== "string") { url2 = urlOrConnectionString; if (blobNameOrOptions && typeof blobNameOrOptions !== "string") { options = blobNameOrOptions; } - pipeline = (0, Pipeline_js_1.newPipeline)(new AnonymousCredential_js_1.AnonymousCredential(), options); + pipeline2 = (0, Pipeline_js_1.newPipeline)(new AnonymousCredential_js_1.AnonymousCredential(), options); } else if (credentialOrPipelineOrContainerName && typeof credentialOrPipelineOrContainerName === "string" && blobNameOrOptions && typeof blobNameOrOptions === "string") { const containerName = credentialOrPipelineOrContainerName; const blobName = blobNameOrOptions; @@ -69994,20 +69994,20 @@ var require_Clients = __commonJS({ if (!options.proxyOptions) { options.proxyOptions = (0, core_rest_pipeline_1.getDefaultProxySettings)(extractedCreds.proxyUri); } - pipeline = (0, Pipeline_js_1.newPipeline)(sharedKeyCredential, options); + pipeline2 = (0, Pipeline_js_1.newPipeline)(sharedKeyCredential, options); } else { throw new Error("Account connection string is only supported in Node.js environment"); } } else if (extractedCreds.kind === "SASConnString") { url2 = (0, utils_common_js_1.appendToURLPath)((0, utils_common_js_1.appendToURLPath)(extractedCreds.url, encodeURIComponent(containerName)), encodeURIComponent(blobName)) + "?" + extractedCreds.accountSas; - pipeline = (0, Pipeline_js_1.newPipeline)(new AnonymousCredential_js_1.AnonymousCredential(), options); + pipeline2 = (0, Pipeline_js_1.newPipeline)(new AnonymousCredential_js_1.AnonymousCredential(), options); } else { throw new Error("Connection string must be either an Account connection string or a SAS connection string"); } } else { throw new Error("Expecting non-empty strings for containerName and blobName parameters"); } - super(url2, pipeline); + super(url2, pipeline2); this.blockBlobContext = this.storageClientContext.blockBlob; this._blobContext = this.storageClientContext.blob; } @@ -70579,19 +70579,19 @@ var require_Clients = __commonJS({ */ pageBlobContext; constructor(urlOrConnectionString, credentialOrPipelineOrContainerName, blobNameOrOptions, options) { - let pipeline; + let pipeline2; let url2; options = options || {}; if ((0, Pipeline_js_1.isPipelineLike)(credentialOrPipelineOrContainerName)) { url2 = urlOrConnectionString; - pipeline = credentialOrPipelineOrContainerName; + pipeline2 = credentialOrPipelineOrContainerName; } else if (core_util_1.isNodeLike && credentialOrPipelineOrContainerName instanceof StorageSharedKeyCredential_js_1.StorageSharedKeyCredential || credentialOrPipelineOrContainerName instanceof AnonymousCredential_js_1.AnonymousCredential || (0, core_auth_1.isTokenCredential)(credentialOrPipelineOrContainerName)) { url2 = urlOrConnectionString; options = blobNameOrOptions; - pipeline = (0, Pipeline_js_1.newPipeline)(credentialOrPipelineOrContainerName, options); + pipeline2 = (0, Pipeline_js_1.newPipeline)(credentialOrPipelineOrContainerName, options); } else if (!credentialOrPipelineOrContainerName && typeof credentialOrPipelineOrContainerName !== "string") { url2 = urlOrConnectionString; - pipeline = (0, Pipeline_js_1.newPipeline)(new AnonymousCredential_js_1.AnonymousCredential(), options); + pipeline2 = (0, Pipeline_js_1.newPipeline)(new AnonymousCredential_js_1.AnonymousCredential(), options); } else if (credentialOrPipelineOrContainerName && typeof credentialOrPipelineOrContainerName === "string" && blobNameOrOptions && typeof blobNameOrOptions === "string") { const containerName = credentialOrPipelineOrContainerName; const blobName = blobNameOrOptions; @@ -70603,20 +70603,20 @@ var require_Clients = __commonJS({ if (!options.proxyOptions) { options.proxyOptions = (0, core_rest_pipeline_1.getDefaultProxySettings)(extractedCreds.proxyUri); } - pipeline = (0, Pipeline_js_1.newPipeline)(sharedKeyCredential, options); + pipeline2 = (0, Pipeline_js_1.newPipeline)(sharedKeyCredential, options); } else { throw new Error("Account connection string is only supported in Node.js environment"); } } else if (extractedCreds.kind === "SASConnString") { url2 = (0, utils_common_js_1.appendToURLPath)((0, utils_common_js_1.appendToURLPath)(extractedCreds.url, encodeURIComponent(containerName)), encodeURIComponent(blobName)) + "?" + extractedCreds.accountSas; - pipeline = (0, Pipeline_js_1.newPipeline)(new AnonymousCredential_js_1.AnonymousCredential(), options); + pipeline2 = (0, Pipeline_js_1.newPipeline)(new AnonymousCredential_js_1.AnonymousCredential(), options); } else { throw new Error("Connection string must be either an Account connection string or a SAS connection string"); } } else { throw new Error("Expecting non-empty strings for containerName and blobName parameters"); } - super(url2, pipeline); + super(url2, pipeline2); this.pageBlobContext = this.storageClientContext.pageBlob; } /** @@ -71681,10 +71681,10 @@ var require_BlobBatch = __commonJS({ accountKey: credential.accountKey }), { phase: "Sign" }); } - const pipeline = new Pipeline_js_1.Pipeline([]); - pipeline._credential = credential; - pipeline._corePipeline = corePipeline; - return pipeline; + const pipeline2 = new Pipeline_js_1.Pipeline([]); + pipeline2._credential = credential; + pipeline2._corePipeline = corePipeline; + return pipeline2; } appendSubRequestToBody(request3) { this.body += [ @@ -71776,15 +71776,15 @@ var require_BlobBatchClient = __commonJS({ var BlobBatchClient = class { serviceOrContainerContext; constructor(url2, credentialOrPipeline, options) { - let pipeline; + let pipeline2; if ((0, Pipeline_js_1.isPipelineLike)(credentialOrPipeline)) { - pipeline = credentialOrPipeline; + pipeline2 = credentialOrPipeline; } else if (!credentialOrPipeline) { - pipeline = (0, Pipeline_js_1.newPipeline)(new AnonymousCredential_js_1.AnonymousCredential(), options); + pipeline2 = (0, Pipeline_js_1.newPipeline)(new AnonymousCredential_js_1.AnonymousCredential(), options); } else { - pipeline = (0, Pipeline_js_1.newPipeline)(credentialOrPipeline, options); + pipeline2 = (0, Pipeline_js_1.newPipeline)(credentialOrPipeline, options); } - const storageClientContext = new StorageContextClient_js_1.StorageContextClient(url2, (0, Pipeline_js_1.getCoreClientOptions)(pipeline)); + const storageClientContext = new StorageContextClient_js_1.StorageContextClient(url2, (0, Pipeline_js_1.getCoreClientOptions)(pipeline2)); const path29 = (0, utils_common_js_1.getURLPath)(url2); if (path29 && path29 !== "/") { this.serviceOrContainerContext = storageClientContext.container; @@ -71947,18 +71947,18 @@ var require_ContainerClient = __commonJS({ return this._containerName; } constructor(urlOrConnectionString, credentialOrPipelineOrContainerName, options) { - let pipeline; + let pipeline2; let url2; options = options || {}; if ((0, Pipeline_js_1.isPipelineLike)(credentialOrPipelineOrContainerName)) { url2 = urlOrConnectionString; - pipeline = credentialOrPipelineOrContainerName; + pipeline2 = credentialOrPipelineOrContainerName; } else if (core_util_1.isNodeLike && credentialOrPipelineOrContainerName instanceof StorageSharedKeyCredential_js_1.StorageSharedKeyCredential || credentialOrPipelineOrContainerName instanceof AnonymousCredential_js_1.AnonymousCredential || (0, core_auth_1.isTokenCredential)(credentialOrPipelineOrContainerName)) { url2 = urlOrConnectionString; - pipeline = (0, Pipeline_js_1.newPipeline)(credentialOrPipelineOrContainerName, options); + pipeline2 = (0, Pipeline_js_1.newPipeline)(credentialOrPipelineOrContainerName, options); } else if (!credentialOrPipelineOrContainerName && typeof credentialOrPipelineOrContainerName !== "string") { url2 = urlOrConnectionString; - pipeline = (0, Pipeline_js_1.newPipeline)(new AnonymousCredential_js_1.AnonymousCredential(), options); + pipeline2 = (0, Pipeline_js_1.newPipeline)(new AnonymousCredential_js_1.AnonymousCredential(), options); } else if (credentialOrPipelineOrContainerName && typeof credentialOrPipelineOrContainerName === "string") { const containerName = credentialOrPipelineOrContainerName; const extractedCreds = (0, utils_common_js_1.extractConnectionStringParts)(urlOrConnectionString); @@ -71969,20 +71969,20 @@ var require_ContainerClient = __commonJS({ if (!options.proxyOptions) { options.proxyOptions = (0, core_rest_pipeline_1.getDefaultProxySettings)(extractedCreds.proxyUri); } - pipeline = (0, Pipeline_js_1.newPipeline)(sharedKeyCredential, options); + pipeline2 = (0, Pipeline_js_1.newPipeline)(sharedKeyCredential, options); } else { throw new Error("Account connection string is only supported in Node.js environment"); } } else if (extractedCreds.kind === "SASConnString") { url2 = (0, utils_common_js_1.appendToURLPath)(extractedCreds.url, encodeURIComponent(containerName)) + "?" + extractedCreds.accountSas; - pipeline = (0, Pipeline_js_1.newPipeline)(new AnonymousCredential_js_1.AnonymousCredential(), options); + pipeline2 = (0, Pipeline_js_1.newPipeline)(new AnonymousCredential_js_1.AnonymousCredential(), options); } else { throw new Error("Connection string must be either an Account connection string or a SAS connection string"); } } else { throw new Error("Expecting non-empty strings for containerName parameter"); } - super(url2, pipeline); + super(url2, pipeline2); this._containerName = this.getContainerNameFromUrl(); this.containerContext = this.storageClientContext.container; } @@ -73660,28 +73660,28 @@ var require_BlobServiceClient = __commonJS({ if (!options.proxyOptions) { options.proxyOptions = (0, core_rest_pipeline_1.getDefaultProxySettings)(extractedCreds.proxyUri); } - const pipeline = (0, Pipeline_js_1.newPipeline)(sharedKeyCredential, options); - return new _BlobServiceClient(extractedCreds.url, pipeline); + const pipeline2 = (0, Pipeline_js_1.newPipeline)(sharedKeyCredential, options); + return new _BlobServiceClient(extractedCreds.url, pipeline2); } else { throw new Error("Account connection string is only supported in Node.js environment"); } } else if (extractedCreds.kind === "SASConnString") { - const pipeline = (0, Pipeline_js_1.newPipeline)(new AnonymousCredential_js_1.AnonymousCredential(), options); - return new _BlobServiceClient(extractedCreds.url + "?" + extractedCreds.accountSas, pipeline); + const pipeline2 = (0, Pipeline_js_1.newPipeline)(new AnonymousCredential_js_1.AnonymousCredential(), options); + return new _BlobServiceClient(extractedCreds.url + "?" + extractedCreds.accountSas, pipeline2); } else { throw new Error("Connection string must be either an Account connection string or a SAS connection string"); } } constructor(url2, credentialOrPipeline, options) { - let pipeline; + let pipeline2; if ((0, Pipeline_js_1.isPipelineLike)(credentialOrPipeline)) { - pipeline = credentialOrPipeline; + pipeline2 = credentialOrPipeline; } else if (core_util_1.isNodeLike && credentialOrPipeline instanceof StorageSharedKeyCredential_js_1.StorageSharedKeyCredential || credentialOrPipeline instanceof AnonymousCredential_js_1.AnonymousCredential || (0, core_auth_1.isTokenCredential)(credentialOrPipeline)) { - pipeline = (0, Pipeline_js_1.newPipeline)(credentialOrPipeline, options); + pipeline2 = (0, Pipeline_js_1.newPipeline)(credentialOrPipeline, options); } else { - pipeline = (0, Pipeline_js_1.newPipeline)(new AnonymousCredential_js_1.AnonymousCredential(), options); + pipeline2 = (0, Pipeline_js_1.newPipeline)(new AnonymousCredential_js_1.AnonymousCredential(), options); } - super(url2, pipeline); + super(url2, pipeline2); this.serviceContext = this.storageClientContext.service; } /** @@ -75082,8 +75082,8 @@ var require_downloadUtils = __commonJS({ var abort_controller_1 = require_dist4(); function pipeResponseToStream(response, output) { return __awaiter2(this, void 0, void 0, function* () { - const pipeline = util3.promisify(stream2.pipeline); - yield pipeline(response.message, output); + const pipeline2 = util3.promisify(stream2.pipeline); + yield pipeline2(response.message, output); }); } var DownloadProgress = class { @@ -82212,12 +82212,12 @@ var require_tool_cache = __commonJS({ core31.debug(`Failed to download from "${url2}". Code(${response.message.statusCode}) Message(${response.message.statusMessage})`); throw err; } - const pipeline = util3.promisify(stream2.pipeline); + const pipeline2 = util3.promisify(stream2.pipeline); const responseMessageFactory = _getGlobal("TEST_DOWNLOAD_TOOL_RESPONSE_MESSAGE_FACTORY", () => response.message); const readStream = responseMessageFactory(); let succeeded = false; try { - yield pipeline(readStream, fs31.createWriteStream(dest)); + yield pipeline2(readStream, fs31.createWriteStream(dest)); core31.debug("download complete"); succeeded = true; return dest; @@ -100809,7 +100809,7 @@ var require_pipeline4 = __commonJS({ } } } - function pipeline(...streams) { + function pipeline2(...streams) { return pipelineImpl(streams, once(popCallback(streams))); } function pipelineImpl(streams, callback, opts) { @@ -101075,7 +101075,7 @@ var require_pipeline4 = __commonJS({ } module2.exports = { pipelineImpl, - pipeline + pipeline: pipeline2 }; } }); @@ -101084,7 +101084,7 @@ var require_pipeline4 = __commonJS({ var require_compose = __commonJS({ "node_modules/readable-stream/lib/internal/streams/compose.js"(exports2, module2) { "use strict"; - var { pipeline } = require_pipeline4(); + var { pipeline: pipeline2 } = require_pipeline4(); var Duplex = require_duplex(); var { destroyer } = require_destroy2(); var { @@ -101144,7 +101144,7 @@ var require_compose = __commonJS({ } } const head = streams[0]; - const tail = pipeline(streams, onfinished); + const tail = pipeline2(streams, onfinished); const writable = !!(isWritable(head) || isWritableStream(head) || isTransformStream(head)); const readable = !!(isReadable(tail) || isReadableStream(tail) || isTransformStream(tail)); d = new Duplex({ @@ -101687,7 +101687,7 @@ var require_promises = __commonJS({ var { pipelineImpl: pl } = require_pipeline4(); var { finished } = require_end_of_stream(); require_stream2(); - function pipeline(...streams) { + function pipeline2(...streams) { return new Promise2((resolve14, reject) => { let signal; let end; @@ -101715,7 +101715,7 @@ var require_promises = __commonJS({ } module2.exports = { finished, - pipeline + pipeline: pipeline2 }; } }); @@ -101735,7 +101735,7 @@ var require_stream2 = __commonJS({ } = require_errors4(); var compose = require_compose(); var { setDefaultHighWaterMark, getDefaultHighWaterMark } = require_state3(); - var { pipeline } = require_pipeline4(); + var { pipeline: pipeline2 } = require_pipeline4(); var { destroyer } = require_destroy2(); var eos = require_end_of_stream(); var promises6 = require_promises(); @@ -101799,7 +101799,7 @@ var require_stream2 = __commonJS({ Stream.Duplex = require_duplex(); Stream.Transform = require_transform(); Stream.PassThrough = require_passthrough2(); - Stream.pipeline = pipeline; + Stream.pipeline = pipeline2; var { addAbortSignal } = require_add_abort_signal(); Stream.addAbortSignal = addAbortSignal; Stream.finished = eos; @@ -101815,7 +101815,7 @@ var require_stream2 = __commonJS({ return promises6; } }); - ObjectDefineProperty(pipeline, customPromisify, { + ObjectDefineProperty(pipeline2, customPromisify, { __proto__: null, enumerable: true, get() { @@ -109038,13 +109038,13 @@ var require_streamx = __commonJS({ } function pipelinePromise(...streams) { return new Promise((resolve14, reject) => { - return pipeline(...streams, (err) => { + return pipeline2(...streams, (err) => { if (err) return reject(err); resolve14(); }); }); } - function pipeline(stream2, ...streams) { + function pipeline2(stream2, ...streams) { const all = Array.isArray(stream2) ? [...stream2, ...streams] : [stream2, ...streams]; const done = all.length && typeof all[all.length - 1] === "function" ? all.pop() : null; if (all.length < 2) throw new Error("Pipeline requires at least 2 streams"); @@ -109129,7 +109129,7 @@ var require_streamx = __commonJS({ return s._writev !== Writable.prototype._writev && s._writev !== Duplex.prototype._writev; } module2.exports = { - pipeline, + pipeline: pipeline2, pipelinePromise, isStream: isStream2, isStreamx, @@ -150565,10 +150565,12 @@ async function extractTarZst(tar, dest, tarVersion, logger) { reject(new Error(`Error while extracting tar: ${err}`)); }); if (tar instanceof stream.Readable) { - tar.pipe(tarProcess.stdin).on("error", (err) => { - reject( - new Error(`Error while downloading and extracting tar: ${err}`) - ); + stream.pipeline(tar, tarProcess.stdin, (err) => { + if (err) { + reject( + new Error(`Error while downloading and extracting tar: ${err}`) + ); + } }); } tarProcess.on("exit", (code) => { @@ -150615,6 +150617,7 @@ var toolcache2 = __toESM(require_tool_cache()); var import_follow_redirects = __toESM(require_follow_redirects()); var semver8 = __toESM(require_semver2()); var STREAMING_HIGH_WATERMARK_BYTES = 4 * 1024 * 1024; +var STREAMING_STALL_TIMEOUT_MS = 5 * 60 * 1e3; var TOOLCACHE_TOOL_NAME = "CodeQL"; async function downloadAndExtract(codeqlURL, compressionMethod, dest, authorization, headers, tarVersion, logger) { logger.info( @@ -150692,8 +150695,8 @@ async function downloadAndExtractZstdWithStreaming(codeqlURL, dest, authorizatio authorization ? { authorization } : {}, headers ); - const response = await new Promise( - (resolve14) => import_follow_redirects.https.get( + const response = await new Promise((resolve14, reject) => { + const request3 = import_follow_redirects.https.get( codeqlURL, { headers, @@ -150703,9 +150706,18 @@ async function downloadAndExtractZstdWithStreaming(codeqlURL, dest, authorizatio agent }, (r) => resolve14(r) - ) - ); + ); + request3.on("error", reject); + request3.setTimeout(STREAMING_STALL_TIMEOUT_MS, () => { + request3.destroy( + new Error( + `No data received for ${formatDuration(STREAMING_STALL_TIMEOUT_MS)}.` + ) + ); + }); + }); if (response.statusCode !== 200) { + response.resume(); throw new Error( `Failed to download CodeQL bundle from ${codeqlURL}. HTTP status code: ${response.statusCode}.` ); diff --git a/src/tar.test.ts b/src/tar.test.ts new file mode 100644 index 0000000000..48f4e866d3 --- /dev/null +++ b/src/tar.test.ts @@ -0,0 +1,33 @@ +import * as path from "path"; +import * as stream from "stream"; + +import test from "ava"; + +import { getRunnerLogger } from "./logging"; +import { extractTarZst } from "./tar"; +import { setupTests } from "./testing-utils"; +import { withTmpDir } from "./util"; + +setupTests(test); + +test("extractTarZst rejects if the input stream errors", async (t) => { + await withTmpDir(async (tmpDir) => { + const archive = new stream.PassThrough(); + const promise = extractTarZst( + archive, + path.join(tmpDir, "dest"), + { type: "gnu", version: "1.34" }, + getRunnerLogger(true), + ); + + archive.destroy( + Object.assign(new Error("socket hang up"), { + code: "ECONNRESET", + }), + ); + + await t.throwsAsync(promise, { + message: /Error while downloading and extracting tar/, + }); + }); +}); diff --git a/src/tar.ts b/src/tar.ts index 723716b016..3a0d79cc64 100644 --- a/src/tar.ts +++ b/src/tar.ts @@ -194,10 +194,15 @@ export async function extractTarZst( }); if (tar instanceof stream.Readable) { - tar.pipe(tarProcess.stdin).on("error", (err) => { - reject( - new Error(`Error while downloading and extracting tar: ${err}`), - ); + // Use `pipeline` rather than `pipe` so that an error on either stream is reported here + // rather than being emitted as an unhandled `error` event, and so that `tar`'s standard + // input is closed if the download fails partway through. + stream.pipeline(tar, tarProcess.stdin, (err) => { + if (err) { + reject( + new Error(`Error while downloading and extracting tar: ${err}`), + ); + } }); } diff --git a/src/tools-download.test.ts b/src/tools-download.test.ts index e17d38c5be..66fe0e72e4 100644 --- a/src/tools-download.test.ts +++ b/src/tools-download.test.ts @@ -38,6 +38,43 @@ test.serial( }, ); +test.serial( + "downloadAndExtract falls back to downloading before extracting if streaming fails", + async (t) => { + await withTmpDir(async (tmpDir) => { + sinon.stub(process, "platform").value("linux"); + const archivePath = path.join(tmpDir, "codeql-bundle.tar.zst"); + const destination = path.join(tmpDir, "codeql"); + const downloadTool = sinon + .stub(toolcache, "downloadTool") + .resolves(archivePath); + const extract = sinon.stub(tar, "extract").resolves(destination); + const extractTarZst = sinon.stub(tar, "extractTarZst").resolves(); + const request = nock("https://example.com") + .get("/codeql-bundle.tar.zst") + .replyWithError( + Object.assign(new Error("socket hang up"), { code: "ECONNRESET" }), + ); + + const statusReport = await downloadAndExtract( + "https://example.com/codeql-bundle.tar.zst", + "zstd", + destination, + undefined, + {}, + { type: "gnu", version: "1.34" }, + getRunnerLogger(true), + ); + + t.assert(Number.isInteger(statusReport.downloadDurationMs)); + t.true(request.isDone()); + t.false(extractTarZst.called); + t.true(downloadTool.calledOnce); + t.true(extract.calledOnce); + }); + }, +); + test.serial( "downloadAndExtract omits the download duration when streaming extraction", async (t) => { diff --git a/src/tools-download.ts b/src/tools-download.ts index c19cedb13e..9b2fa8723a 100644 --- a/src/tools-download.ts +++ b/src/tools-download.ts @@ -19,6 +19,12 @@ import { cleanUpPath, getErrorMessage, getRequiredEnvParam } from "./util"; */ const STREAMING_HIGH_WATERMARK_BYTES = 4 * 1024 * 1024; // 4 MiB +/** + * How long the streaming download of the CodeQL tools may stall for before we abort it. This + * applies both to establishing the connection and to gaps between chunks of the response body. + */ +const STREAMING_STALL_TIMEOUT_MS = 5 * 60 * 1000; // 5 minutes + /** * The name of the tool cache directory for the CodeQL tools. */ @@ -137,8 +143,8 @@ async function downloadAndExtractZstdWithStreaming( authorization ? { authorization } : {}, headers, ); - const response = await new Promise((resolve) => - https.get( + const response = await new Promise((resolve, reject) => { + const request = https.get( codeqlURL, { headers, @@ -148,10 +154,24 @@ async function downloadAndExtractZstdWithStreaming( agent, } as unknown as RequestOptions, (r) => resolve(r), - ), - ); + ); + // Without this listener, connection failures such as `ECONNRESET` are emitted as unhandled + // `error` events, which terminate the process instead of letting us fall back to downloading + // the bundle before extracting it. This listener stays attached after the response arrives, so + // it also handles errors that occur while the response is being streamed. + request.on("error", reject); + request.setTimeout(STREAMING_STALL_TIMEOUT_MS, () => { + request.destroy( + new Error( + `No data received for ${formatDuration(STREAMING_STALL_TIMEOUT_MS)}.`, + ), + ); + }); + }); if (response.statusCode !== 200) { + // Discard the response body so that the connection can be released. + response.resume(); throw new Error( `Failed to download CodeQL bundle from ${codeqlURL}. HTTP status code: ${response.statusCode}.`, ); From 155e5229973b426bd1ae2f83bb1bf42417fa2a8f Mon Sep 17 00:00:00 2001 From: sim Date: Thu, 30 Jul 2026 18:48:10 +0100 Subject: [PATCH 33/35] Link the PR from the changelog entry Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c461878c51..36092606b4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ See the [releases page](https://github.com/github/codeql-action/releases) for th ## [UNRELEASED] -- Fixed a bug where a network error while streaming the download of the CodeQL bundle could terminate the `init` Action instead of falling back to downloading the bundle before extracting it. [#3367](https://github.com/github/codeql-action/issues/3367) +- Fixed a bug where a network error while streaming the download of the CodeQL bundle could terminate the `init` Action instead of falling back to downloading the bundle before extracting it. [#4061](https://github.com/github/codeql-action/pull/4061) ## 4.37.4 - 29 Jul 2026 From c29563eeaafbc75499c7bb0d74bf77b3506c1cbd Mon Sep 17 00:00:00 2001 From: Sam Robson Date: Fri, 31 Jul 2026 10:10:39 +0100 Subject: [PATCH 34/35] ci: use federated enterprise release PAT --- .../workflows/update-supported-enterprise-server-versions.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/update-supported-enterprise-server-versions.yml b/.github/workflows/update-supported-enterprise-server-versions.yml index 01cd6ab8fb..ee2649ad0e 100644 --- a/.github/workflows/update-supported-enterprise-server-versions.yml +++ b/.github/workflows/update-supported-enterprise-server-versions.yml @@ -38,7 +38,7 @@ jobs: uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: repository: github/enterprise-releases - token: ${{ secrets.ENTERPRISE_RELEASE_TOKEN }} + token: ${{ secrets.CODEQL_CI_ENTERPRISE_RELEASE_PAT }} path: ${{ github.workspace }}/enterprise-releases/ sparse-checkout: releases.json From e74600b0d945db9734eb044f95cd43f34b773451 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 3 Aug 2026 09:18:21 +0000 Subject: [PATCH 35/35] Update changelog for v4.37.5 --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 36092606b4..0008822f0e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ See the [releases page](https://github.com/github/codeql-action/releases) for the relevant changes to the CodeQL CLI and language packs. -## [UNRELEASED] +## 4.37.5 - 03 Aug 2026 - Fixed a bug where a network error while streaming the download of the CodeQL bundle could terminate the `init` Action instead of falling back to downloading the bundle before extracting it. [#4061](https://github.com/github/codeql-action/pull/4061)