From 8d2450759287d43b29b439b77121b516fdc83123 Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Mon, 6 Jul 2026 12:55:07 +0100 Subject: [PATCH 01/10] Allow `env` input for relevant functions in `actions-util.ts` --- lib/entry-points.js | 68 ++++++++++++++++++++-------------------- src/actions-util.ts | 76 +++++++++++++++++++++++++-------------------- src/environment.ts | 5 +++ src/util.ts | 1 + 4 files changed, 84 insertions(+), 66 deletions(-) diff --git a/lib/entry-points.js b/lib/entry-points.js index 406916cf6e..e646bc5c46 100644 --- a/lib/entry-points.js +++ b/lib/entry-points.js @@ -144779,7 +144779,8 @@ function initializeEnvironment(version) { function getEnv(env = process.env) { return { getRequired: (name) => getRequiredEnvVar(env, name), - getOptional: (name) => getOptionalEnvVarFrom(env, name) + getOptional: (name) => getOptionalEnvVarFrom(env, name), + entries: () => Object.entries(env) }; } function getRequiredEnvVar(env, paramName) { @@ -145151,31 +145152,31 @@ var getOptionalInput = function(name) { const value = core3.getInput(name); return value.length > 0 ? value : void 0; }; -function getTemporaryDirectory() { - const value = process.env["CODEQL_ACTION_TEMP"]; - return value !== void 0 && value !== "" ? value : getRequiredEnvParam("RUNNER_TEMP" /* RUNNER_TEMP */); +function getTemporaryDirectory(env = getEnv()) { + const value = env.getOptional("CODEQL_ACTION_TEMP" /* TEMP */); + return value !== void 0 && value !== "" ? value : env.getRequired("RUNNER_TEMP" /* RUNNER_TEMP */); } var PR_DIFF_RANGE_JSON_FILENAME = "pr-diff-range.json"; -function getDiffRangesJsonFilePath() { - return path2.join(getTemporaryDirectory(), PR_DIFF_RANGE_JSON_FILENAME); +function getDiffRangesJsonFilePath(env = getEnv()) { + return path2.join(getTemporaryDirectory(env), PR_DIFF_RANGE_JSON_FILENAME); } function getActionVersion() { return "4.37.1"; } -function getWorkflowEventName() { - return getRequiredEnvParam("GITHUB_EVENT_NAME" /* GITHUB_EVENT_NAME */); +function getWorkflowEventName(env = getEnv()) { + return env.getRequired("GITHUB_EVENT_NAME" /* GITHUB_EVENT_NAME */); } -function isRunningLocalAction() { - const relativeScriptPath = getRelativeScriptPath(); +function isRunningLocalAction(env = getEnv()) { + const relativeScriptPath = getRelativeScriptPath(env); return relativeScriptPath.startsWith("..") || path2.isAbsolute(relativeScriptPath); } -function getRelativeScriptPath() { - const runnerTemp = getRequiredEnvParam("RUNNER_TEMP" /* RUNNER_TEMP */); +function getRelativeScriptPath(env) { + const runnerTemp = env.getRequired("RUNNER_TEMP" /* RUNNER_TEMP */); const actionsDirectory = path2.join(path2.dirname(runnerTemp), "_actions"); return path2.relative(actionsDirectory, __filename); } -function getWorkflowEvent() { - const eventJsonFile = getRequiredEnvParam("GITHUB_EVENT_PATH" /* GITHUB_EVENT_PATH */); +function getWorkflowEvent(env = getEnv()) { + const eventJsonFile = env.getRequired("GITHUB_EVENT_PATH" /* GITHUB_EVENT_PATH */); try { return JSON.parse(fs2.readFileSync(eventJsonFile, "utf-8")); } catch (e) { @@ -145231,8 +145232,8 @@ function getUploadValue(input) { return "always"; } } -function getWorkflowRunID() { - const workflowRunIdString = getRequiredEnvParam("GITHUB_RUN_ID" /* GITHUB_RUN_ID */); +function getWorkflowRunID(env = getEnv()) { + const workflowRunIdString = env.getRequired("GITHUB_RUN_ID" /* GITHUB_RUN_ID */); const workflowRunID = parseInt(workflowRunIdString, 10); if (Number.isNaN(workflowRunID)) { throw new Error( @@ -145246,8 +145247,8 @@ function getWorkflowRunID() { } return workflowRunID; } -function getWorkflowRunAttempt() { - const workflowRunAttemptString = getRequiredEnvParam( +function getWorkflowRunAttempt(env = getEnv()) { + const workflowRunAttemptString = env.getRequired( "GITHUB_RUN_ATTEMPT" /* GITHUB_RUN_ATTEMPT */ ); const workflowRunAttempt = parseInt(workflowRunAttemptString, 10); @@ -145300,14 +145301,14 @@ var getFileType = async (filePath) => { throw e; } }; -function isSelfHostedRunner() { - return process.env.RUNNER_ENVIRONMENT === "self-hosted"; +function isSelfHostedRunner(env = getEnv()) { + return env.getOptional("RUNNER_ENVIRONMENT") === "self-hosted"; } -function isDynamicWorkflow() { - return getWorkflowEventName() === "dynamic"; +function isDynamicWorkflow(env = getEnv()) { + return getWorkflowEventName(env) === "dynamic"; } -function isDefaultSetup() { - return isDynamicWorkflow(); +function isDefaultSetup(env = getEnv()) { + return isDynamicWorkflow(env); } function prettyPrintInvocation(cmd, args) { return [cmd, ...args].map((x) => x.includes(" ") ? `'${x}'` : x).join(" "); @@ -145371,8 +145372,9 @@ async function runTool(cmd, args = [], opts = {}) { return stdout; } var persistedInputsKey = "persisted_inputs"; -var persistInputs = function() { - const inputEnvironmentVariables = Object.entries(process.env).filter( +var persistInputs = function(env = getEnv()) { + const entries = env.entries(); + const inputEnvironmentVariables = entries.filter( ([name]) => name.startsWith("INPUT_") ); core3.saveState(persistedInputsKey, JSON.stringify(inputEnvironmentVariables)); @@ -145385,7 +145387,7 @@ var restoreInputs = function() { } } }; -function getPullRequestBranches() { +function getPullRequestBranches(env = getEnv()) { const pullRequest = github.context.payload.pull_request; if (pullRequest) { return { @@ -145396,8 +145398,8 @@ function getPullRequestBranches() { head: pullRequest.head.label }; } - const codeScanningRef = process.env.CODE_SCANNING_REF; - const codeScanningBaseBranch = process.env.CODE_SCANNING_BASE_BRANCH; + const codeScanningRef = env.getOptional("CODE_SCANNING_REF"); + const codeScanningBaseBranch = env.getOptional("CODE_SCANNING_BASE_BRANCH"); if (codeScanningRef && codeScanningBaseBranch) { return { base: codeScanningBaseBranch, @@ -145408,8 +145410,8 @@ function getPullRequestBranches() { } return void 0; } -function isAnalyzingPullRequest() { - return getPullRequestBranches() !== void 0; +function isAnalyzingPullRequest(env = getEnv()) { + return getPullRequestBranches(env) !== void 0; } var qualityCategoryMapping = { "c#": "csharp", @@ -145421,8 +145423,8 @@ var qualityCategoryMapping = { typescript: "javascript-typescript", kotlin: "java-kotlin" }; -function fixCodeQualityCategory(logger, category) { - if (category !== void 0 && isDefaultSetup() && category.startsWith("/language:")) { +function fixCodeQualityCategory(logger, category, env = getEnv()) { + if (category !== void 0 && isDefaultSetup(env) && category.startsWith("/language:")) { const language = category.substring("/language:".length); const mappedLanguage = qualityCategoryMapping[language]; if (mappedLanguage) { diff --git a/src/actions-util.ts b/src/actions-util.ts index d7fbacbf3e..251e804e70 100644 --- a/src/actions-util.ts +++ b/src/actions-util.ts @@ -7,12 +7,13 @@ import * as github from "@actions/github"; import * as io from "@actions/io"; import type { Config } from "./config-utils"; +import { Env, EnvVar } from "./environment"; import { Logger } from "./logging"; import { doesDirectoryExist, getCodeQLDatabasePath, - getRequiredEnvParam, ConfigurationError, + getEnv, } from "./util"; /** @@ -83,17 +84,22 @@ export const getOptionalInput = function (name: string): string | undefined { return value.length > 0 ? value : undefined; }; -export function getTemporaryDirectory(): string { - const value = process.env["CODEQL_ACTION_TEMP"]; +/** + * Gets the temporary directory used by the CodeQL Action. This will either be the temporary + * directory that has been set in `CODEQL_ACTION_TEMP` by e.g. a previous step, or the + * value of `RUNNER_TEMP` otherwise. + */ +export function getTemporaryDirectory(env: Env = getEnv()): string { + const value = env.getOptional(EnvVar.TEMP); return value !== undefined && value !== "" ? value - : getRequiredEnvParam(ActionsEnvVars.RUNNER_TEMP); + : env.getRequired(ActionsEnvVars.RUNNER_TEMP); } const PR_DIFF_RANGE_JSON_FILENAME = "pr-diff-range.json"; -export function getDiffRangesJsonFilePath(): string { - return path.join(getTemporaryDirectory(), PR_DIFF_RANGE_JSON_FILENAME); +export function getDiffRangesJsonFilePath(env: Env = getEnv()): string { + return path.join(getTemporaryDirectory(env), PR_DIFF_RANGE_JSON_FILENAME); } export function getActionVersion(): string { @@ -105,16 +111,16 @@ export function getActionVersion(): string { * * This will be "dynamic" for default setup workflow runs. */ -export function getWorkflowEventName() { - return getRequiredEnvParam(ActionsEnvVars.GITHUB_EVENT_NAME); +export function getWorkflowEventName(env: Env = getEnv()) { + return env.getRequired(ActionsEnvVars.GITHUB_EVENT_NAME); } /** * Returns whether the current workflow is executing a local copy of the Action, e.g. we're running * a workflow on the codeql-action repo itself. */ -export function isRunningLocalAction(): boolean { - const relativeScriptPath = getRelativeScriptPath(); +export function isRunningLocalAction(env: Env = getEnv()): boolean { + const relativeScriptPath = getRelativeScriptPath(env); return ( relativeScriptPath.startsWith("..") || path.isAbsolute(relativeScriptPath) ); @@ -125,15 +131,15 @@ export function isRunningLocalAction(): boolean { * * This can be used to get the Action's name or tell if we're running a local Action. */ -function getRelativeScriptPath(): string { - const runnerTemp = getRequiredEnvParam(ActionsEnvVars.RUNNER_TEMP); +function getRelativeScriptPath(env: Env): string { + const runnerTemp = env.getRequired(ActionsEnvVars.RUNNER_TEMP); const actionsDirectory = path.join(path.dirname(runnerTemp), "_actions"); return path.relative(actionsDirectory, __filename); } /** Returns the contents of `GITHUB_EVENT_PATH` as a JSON object. */ -export function getWorkflowEvent(): any { - const eventJsonFile = getRequiredEnvParam(ActionsEnvVars.GITHUB_EVENT_PATH); +export function getWorkflowEvent(env: Env = getEnv()): any { + const eventJsonFile = env.getRequired(ActionsEnvVars.GITHUB_EVENT_PATH); try { return JSON.parse(fs.readFileSync(eventJsonFile, "utf-8")); } catch (e) { @@ -202,8 +208,8 @@ export function getUploadValue(input: string | undefined): UploadKind { /** * Get the workflow run ID. */ -export function getWorkflowRunID(): number { - const workflowRunIdString = getRequiredEnvParam(ActionsEnvVars.GITHUB_RUN_ID); +export function getWorkflowRunID(env: Env = getEnv()): number { + const workflowRunIdString = env.getRequired(ActionsEnvVars.GITHUB_RUN_ID); const workflowRunID = parseInt(workflowRunIdString, 10); if (Number.isNaN(workflowRunID)) { throw new Error( @@ -221,8 +227,8 @@ export function getWorkflowRunID(): number { /** * Get the workflow run attempt number. */ -export function getWorkflowRunAttempt(): number { - const workflowRunAttemptString = getRequiredEnvParam( +export function getWorkflowRunAttempt(env: Env = getEnv()): number { + const workflowRunAttemptString = env.getRequired( ActionsEnvVars.GITHUB_RUN_ATTEMPT, ); const workflowRunAttempt = parseInt(workflowRunAttemptString, 10); @@ -290,18 +296,18 @@ export const getFileType = async (filePath: string): Promise => { } }; -export function isSelfHostedRunner() { - return process.env.RUNNER_ENVIRONMENT === "self-hosted"; +export function isSelfHostedRunner(env: Env = getEnv()) { + return env.getOptional("RUNNER_ENVIRONMENT") === "self-hosted"; } /** Determines whether the workflow trigger is `dynamic`. */ -export function isDynamicWorkflow(): boolean { - return getWorkflowEventName() === "dynamic"; +export function isDynamicWorkflow(env: Env = getEnv()): boolean { + return getWorkflowEventName(env) === "dynamic"; } /** Determines whether we are running in default setup. */ -export function isDefaultSetup(): boolean { - return isDynamicWorkflow(); +export function isDefaultSetup(env: Env = getEnv()): boolean { + return isDynamicWorkflow(env); } export function prettyPrintInvocation(cmd: string, args: string[]): string { @@ -399,9 +405,10 @@ const persistedInputsKey = "persisted_inputs"; * This would be simplified if actions/runner#3514 is addressed. * https://github.com/actions/runner/issues/3514 */ -export const persistInputs = function () { - const inputEnvironmentVariables = Object.entries(process.env).filter( - ([name]) => name.startsWith("INPUT_"), +export const persistInputs = function (env: Env = getEnv()) { + const entries = env.entries(); + const inputEnvironmentVariables = entries.filter(([name]) => + name.startsWith("INPUT_"), ); core.saveState(persistedInputsKey, JSON.stringify(inputEnvironmentVariables)); }; @@ -429,7 +436,9 @@ export interface PullRequestBranches { * @returns the base and head branches of the pull request, or undefined if * we are not analyzing a pull request. */ -export function getPullRequestBranches(): PullRequestBranches | undefined { +export function getPullRequestBranches( + env: Env = getEnv(), +): PullRequestBranches | undefined { const pullRequest = github.context.payload.pull_request; if (pullRequest) { return { @@ -443,8 +452,8 @@ export function getPullRequestBranches(): PullRequestBranches | undefined { // PR analysis under Default Setup does not have the pull_request context, // but it should set CODE_SCANNING_REF and CODE_SCANNING_BASE_BRANCH. - const codeScanningRef = process.env.CODE_SCANNING_REF; - const codeScanningBaseBranch = process.env.CODE_SCANNING_BASE_BRANCH; + const codeScanningRef = env.getOptional("CODE_SCANNING_REF"); + const codeScanningBaseBranch = env.getOptional("CODE_SCANNING_BASE_BRANCH"); if (codeScanningRef && codeScanningBaseBranch) { return { base: codeScanningBaseBranch, @@ -459,8 +468,8 @@ export function getPullRequestBranches(): PullRequestBranches | undefined { /** * Returns whether we are analyzing a pull request. */ -export function isAnalyzingPullRequest(): boolean { - return getPullRequestBranches() !== undefined; +export function isAnalyzingPullRequest(env: Env = getEnv()): boolean { + return getPullRequestBranches(env) !== undefined; } /** @@ -484,13 +493,14 @@ const qualityCategoryMapping: Record = { export function fixCodeQualityCategory( logger: Logger, category?: string, + env: Env = getEnv(), ): string | undefined { // The `category` should always be set by Default Setup. We perform this check // to avoid potential issues if Code Quality supports Advanced Setup in the future // and before this workaround is removed. if ( category !== undefined && - isDefaultSetup() && + isDefaultSetup(env) && category.startsWith("/language:") ) { const language = category.substring("/language:".length); diff --git a/src/environment.ts b/src/environment.ts index c0ca050b03..9912f03825 100644 --- a/src/environment.ts +++ b/src/environment.ts @@ -83,6 +83,9 @@ export enum EnvVar { /** Whether to suppress the warning if the current CLI will soon be unsupported. */ SUPPRESS_DEPRECATED_SOON_WARNING = "CODEQL_ACTION_SUPPRESS_DEPRECATED_SOON_WARNING", + /** Used to dictate or persist the temporary directory used by the CodeQL Action. */ + TEMP = "CODEQL_ACTION_TEMP", + /** Whether to disable uploading SARIF results or status reports to the GitHub API */ TEST_MODE = "CODEQL_ACTION_TEST_MODE", @@ -167,4 +170,6 @@ export interface Env { getRequired(name: string): string; /** Gets the value for `name`, or `undefined` if it isn't set or empty. */ getOptional(name: string): string | undefined; + /** Gets the entries of the underlying `ProcessEnv`. */ + entries(): Array<[string, string | undefined]>; } diff --git a/src/util.ts b/src/util.ts index 7f52456608..1e79382f6e 100644 --- a/src/util.ts +++ b/src/util.ts @@ -571,6 +571,7 @@ export function getEnv(env: NodeJS.ProcessEnv = process.env): Env { return { getRequired: (name) => getRequiredEnvVar(env, name), getOptional: (name) => getOptionalEnvVarFrom(env, name), + entries: () => Object.entries(env), }; } From 4c868b3e7e64e90d5dd85207c9a6412d99199b72 Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Mon, 6 Jul 2026 13:06:23 +0100 Subject: [PATCH 02/10] Allow setting environment variables --- src/environment.ts | 2 ++ src/testing-utils.ts | 12 ++++++++++-- src/util.ts | 3 +++ 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/src/environment.ts b/src/environment.ts index 9912f03825..03d4870be1 100644 --- a/src/environment.ts +++ b/src/environment.ts @@ -172,4 +172,6 @@ export interface Env { getOptional(name: string): string | undefined; /** Gets the entries of the underlying `ProcessEnv`. */ entries(): Array<[string, string | undefined]>; + /** Sets an environment variable. */ + set(name: string, value: string): void; } diff --git a/src/testing-utils.ts b/src/testing-utils.ts index 748ce40ea3..c6ff0dbdac 100644 --- a/src/testing-utils.ts +++ b/src/testing-utils.ts @@ -331,11 +331,19 @@ export type ActionVarOverrides = Partial< * excluding some that are expected to be set to paths. See `setupActionsVars`. * * @param overrides Overrides for the defaults. + * @param env The environment to set the variables for. */ -export function setupBaseActionsVars(overrides?: ActionVarOverrides) { +export function setupBaseActionsVars( + overrides?: ActionVarOverrides, + env?: Env, +) { const vars = { ...DEFAULT_ACTIONS_VARS, ...overrides }; for (const [key, value] of Object.entries(vars)) { - process.env[key] = value; + if (env) { + env.set(key, value); + } else { + process.env[key] = value; + } } } diff --git a/src/util.ts b/src/util.ts index 1e79382f6e..36f0ce04b0 100644 --- a/src/util.ts +++ b/src/util.ts @@ -572,6 +572,9 @@ export function getEnv(env: NodeJS.ProcessEnv = process.env): Env { getRequired: (name) => getRequiredEnvVar(env, name), getOptional: (name) => getOptionalEnvVarFrom(env, name), entries: () => Object.entries(env), + set: (name, value) => { + env[name] = value; + }, }; } From 1d0e1b20e5f0238c48d4a2bdb81a7e28cffd40e2 Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Thu, 9 Jul 2026 12:20:20 +0100 Subject: [PATCH 03/10] Move env var getters out of `util.ts` --- lib/entry-points.js | 59 ++++++++++++++++++++++++--------------------- src/environment.ts | 54 +++++++++++++++++++++++++++++++++++++++++ src/util.ts | 59 +++------------------------------------------ 3 files changed, 90 insertions(+), 82 deletions(-) diff --git a/lib/entry-points.js b/lib/entry-points.js index e646bc5c46..705ce3f33a 100644 --- a/lib/entry-points.js +++ b/lib/entry-points.js @@ -141462,6 +141462,38 @@ var toolrunner = __toESM(require_toolrunner()); var github = __toESM(require_github()); var io2 = __toESM(require_io()); +// src/environment.ts +function getRequiredEnvVar(env, paramName) { + const value = env[paramName]; + if (value === void 0 || value.length === 0) { + throw new Error(`${paramName} environment variable must be set`); + } + return value; +} +function getRequiredEnvParam(paramName) { + return getRequiredEnvVar(process.env, paramName); +} +function getOptionalEnvVarFrom(env, paramName) { + const value = env[paramName]; + if (value?.trim().length === 0) { + return void 0; + } + return value; +} +function getOptionalEnvVar(paramName) { + return getOptionalEnvVarFrom(process.env, paramName); +} +function getEnv(env = process.env) { + return { + getRequired: (name) => getRequiredEnvVar(env, name), + getOptional: (name) => getOptionalEnvVarFrom(env, name), + entries: () => Object.entries(env), + set: (name, value) => { + env[name] = value; + } + }; +} + // src/util.ts var fs = __toESM(require("fs")); var fsPromises = __toESM(require("fs/promises")); @@ -144776,33 +144808,6 @@ function initializeEnvironment(version) { core2.exportVariable("CODEQL_ACTION_FEATURE_WILL_UPLOAD" /* FEATURE_WILL_UPLOAD */, "true"); core2.exportVariable("CODEQL_ACTION_VERSION" /* VERSION */, version); } -function getEnv(env = process.env) { - return { - getRequired: (name) => getRequiredEnvVar(env, name), - getOptional: (name) => getOptionalEnvVarFrom(env, name), - entries: () => Object.entries(env) - }; -} -function getRequiredEnvVar(env, paramName) { - const value = env[paramName]; - if (value === void 0 || value.length === 0) { - throw new Error(`${paramName} environment variable must be set`); - } - return value; -} -function getRequiredEnvParam(paramName) { - return getRequiredEnvVar(process.env, paramName); -} -function getOptionalEnvVarFrom(env, paramName) { - const value = env[paramName]; - if (value?.trim().length === 0) { - return void 0; - } - return value; -} -function getOptionalEnvVar(paramName) { - return getOptionalEnvVarFrom(process.env, paramName); -} var HTTPError = class extends Error { status; constructor(message, status) { diff --git a/src/environment.ts b/src/environment.ts index 03d4870be1..e2cfe49e8b 100644 --- a/src/environment.ts +++ b/src/environment.ts @@ -164,6 +164,60 @@ export enum EnvVar { RISK_ASSESSMENT_ID = "CODEQL_ACTION_RISK_ASSESSMENT_ID", } +/** + * Gets an environment variable, but throws an error if it is not set. + */ +export function getRequiredEnvVar( + env: NodeJS.ProcessEnv, + paramName: string, +): string { + const value = env[paramName]; + if (value === undefined || value.length === 0) { + throw new Error(`${paramName} environment variable must be set`); + } + return value; +} + +/** + * Get an environment parameter, but throw an error if it is not set. + */ +export function getRequiredEnvParam(paramName: string): string { + return getRequiredEnvVar(process.env, paramName); +} + +/** + * Gets an environment variable, but returns `undefined` if it is not set or empty. + */ +export function getOptionalEnvVarFrom( + env: NodeJS.ProcessEnv, + paramName: string, +): string | undefined { + const value = env[paramName]; + if (value?.trim().length === 0) { + return undefined; + } + return value; +} + +/** + * Get an environment variable, but return `undefined` if it is not set or empty. + */ +export function getOptionalEnvVar(paramName: string): string | undefined { + return getOptionalEnvVarFrom(process.env, paramName); +} + +/** Gets an `Env` instance for `env`, which is `process.env` by default. */ +export function getEnv(env: NodeJS.ProcessEnv = process.env): Env { + return { + getRequired: (name) => getRequiredEnvVar(env, name), + getOptional: (name) => getOptionalEnvVarFrom(env, name), + entries: () => Object.entries(env), + set: (name, value) => { + env[name] = value; + }, + }; +} + /** A wrapper around an environment, to allow abstracting away from `process.env` in tests. */ export interface Env { /** Tries to get the value for `name` and throws if there isn't one. */ diff --git a/src/util.ts b/src/util.ts index 36f0ce04b0..b7d27afae3 100644 --- a/src/util.ts +++ b/src/util.ts @@ -13,11 +13,14 @@ import * as apiCompatibility from "./api-compatibility.json"; import type { CodeQL, VersionInfo } from "./codeql"; import type { Pack } from "./config/db-config"; import type { Config } from "./config-utils"; -import { Env, EnvVar } from "./environment"; +import { EnvVar, getRequiredEnvParam } from "./environment"; import * as json from "./json"; import { Language } from "./languages"; import { Logger } from "./logging"; +// Re-export for backwards compatibility to avoid updating a lot of imports elsewhere. +export { getRequiredEnvParam, getOptionalEnvVar, getEnv } from "./environment"; + /** * The name of the file containing the base database OIDs, as stored in the * root of the database location. @@ -566,60 +569,6 @@ export function initializeEnvironment(version: string) { core.exportVariable(EnvVar.VERSION, version); } -/** Gets an `Env` instance for `env`, which is `process.env` by default. */ -export function getEnv(env: NodeJS.ProcessEnv = process.env): Env { - return { - getRequired: (name) => getRequiredEnvVar(env, name), - getOptional: (name) => getOptionalEnvVarFrom(env, name), - entries: () => Object.entries(env), - set: (name, value) => { - env[name] = value; - }, - }; -} - -/** - * Gets an environment variable, but throws an error if it is not set. - */ -export function getRequiredEnvVar( - env: NodeJS.ProcessEnv, - paramName: string, -): string { - const value = env[paramName]; - if (value === undefined || value.length === 0) { - throw new Error(`${paramName} environment variable must be set`); - } - return value; -} - -/** - * Get an environment parameter, but throw an error if it is not set. - */ -export function getRequiredEnvParam(paramName: string): string { - return getRequiredEnvVar(process.env, paramName); -} - -/** - * Gets an environment variable, but returns `undefined` if it is not set or empty. - */ -export function getOptionalEnvVarFrom( - env: NodeJS.ProcessEnv, - paramName: string, -): string | undefined { - const value = env[paramName]; - if (value?.trim().length === 0) { - return undefined; - } - return value; -} - -/** - * Get an environment variable, but return `undefined` if it is not set or empty. - */ -export function getOptionalEnvVar(paramName: string): string | undefined { - return getOptionalEnvVarFrom(process.env, paramName); -} - export class HTTPError extends Error { public status: number; From 732911a2faf94a456db7b30e7d7d298ec0d6fee1 Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Thu, 9 Jul 2026 12:38:40 +0100 Subject: [PATCH 04/10] Turn `Env` into a class, and add `ReadOnlyEnv` --- lib/entry-points.js | 43 +++++++++++++++++++--------- src/api-client.test.ts | 23 ++++++++------- src/api-client.ts | 12 ++++---- src/environment.ts | 64 +++++++++++++++++++++++++++--------------- 4 files changed, 91 insertions(+), 51 deletions(-) diff --git a/lib/entry-points.js b/lib/entry-points.js index 705ce3f33a..c4bf3a3bda 100644 --- a/lib/entry-points.js +++ b/lib/entry-points.js @@ -141483,15 +141483,32 @@ function getOptionalEnvVarFrom(env, paramName) { function getOptionalEnvVar(paramName) { return getOptionalEnvVarFrom(process.env, paramName); } +var ReadOnlyEnv = class { + constructor(vars) { + this.vars = vars; + } + vars; + /** Tries to get the value for `name` and throws if there isn't one. */ + getRequired(name) { + return getRequiredEnvVar(this.vars, name); + } + /** Gets the value for `name`, or `undefined` if it isn't set or empty. */ + getOptional(name) { + return getOptionalEnvVarFrom(this.vars, name); + } + /** Gets the entries of the underlying `ProcessEnv`. */ + entries() { + return Object.entries(this.vars); + } +}; +var Env = class extends ReadOnlyEnv { + /** Sets an environment variable. */ + set(name, value) { + this.vars[name] = value; + } +}; function getEnv(env = process.env) { - return { - getRequired: (name) => getRequiredEnvVar(env, name), - getOptional: (name) => getOptionalEnvVarFrom(env, name), - entries: () => Object.entries(env), - set: (name, value) => { - env[name] = value; - } - }; + return new Env(env); } // src/util.ts @@ -145613,15 +145630,15 @@ function createApiClientWithDetails(apiDetails, { allowExternal = false } = {}) }) ); } -function getApiDetails() { +function getApiDetails(env = getEnv()) { return { auth: getRequiredInput("token"), - url: getRequiredEnvParam("GITHUB_SERVER_URL" /* GITHUB_SERVER_URL */), - apiURL: getRequiredEnvParam("GITHUB_API_URL" /* GITHUB_API_URL */) + url: env.getRequired("GITHUB_SERVER_URL" /* GITHUB_SERVER_URL */), + apiURL: env.getRequired("GITHUB_API_URL" /* GITHUB_API_URL */) }; } -function getApiClient() { - return createApiClientWithDetails(getApiDetails()); +function getApiClient(env = getEnv()) { + return createApiClientWithDetails(getApiDetails(env)); } function getApiClientWithExternalAuth(apiDetails) { return createApiClientWithDetails(apiDetails, { allowExternal: true }); diff --git a/src/api-client.test.ts b/src/api-client.test.ts index 29cad338f7..affb5b8946 100644 --- a/src/api-client.test.ts +++ b/src/api-client.test.ts @@ -6,7 +6,7 @@ import * as sinon from "sinon"; import * as actionsUtil from "./actions-util"; import * as api from "./api-client"; import { DO_NOT_RETRY_STATUSES } from "./api-client"; -import { setupTests } from "./testing-utils"; +import { getTestEnv, setupTests } from "./testing-utils"; import * as util from "./util"; setupTests(test); @@ -20,16 +20,19 @@ test.serial("getApiClient", async (t) => { const githubStub: sinon.SinonStub = sinon.stub(); pluginStub.returns(githubStub); + const env = getTestEnv(); + env.set( + actionsUtil.ActionsEnvVars.GITHUB_SERVER_URL, + "http://github.localhost", + ); + env.set( + actionsUtil.ActionsEnvVars.GITHUB_API_URL, + "http://api.github.localhost", + ); + sinon.stub(actionsUtil, "getRequiredInput").withArgs("token").returns("xyz"); - const requiredEnvParamStub = sinon.stub(util, "getRequiredEnvParam"); - requiredEnvParamStub - .withArgs("GITHUB_SERVER_URL") - .returns("http://github.localhost"); - requiredEnvParamStub - .withArgs("GITHUB_API_URL") - .returns("http://api.github.localhost"); - - api.getApiClient(); + + api.getApiClient(env); t.assert( githubStub.calledOnceWithExactly({ diff --git a/src/api-client.ts b/src/api-client.ts index 16e4082e90..a4a80cd485 100644 --- a/src/api-client.ts +++ b/src/api-client.ts @@ -7,7 +7,7 @@ import { getActionVersion, getRequiredInput, } from "./actions-util"; -import { EnvVar } from "./environment"; +import { EnvVar, ReadOnlyEnv, getEnv } from "./environment"; import { Logger } from "./logging"; import { getRepositoryNwo, RepositoryNwo } from "./repository"; import { @@ -71,16 +71,16 @@ function createApiClientWithDetails( ); } -export function getApiDetails(): GitHubApiDetails { +export function getApiDetails(env: ReadOnlyEnv = getEnv()): GitHubApiDetails { return { auth: getRequiredInput("token"), - url: getRequiredEnvParam(ActionsEnvVars.GITHUB_SERVER_URL), - apiURL: getRequiredEnvParam(ActionsEnvVars.GITHUB_API_URL), + url: env.getRequired(ActionsEnvVars.GITHUB_SERVER_URL), + apiURL: env.getRequired(ActionsEnvVars.GITHUB_API_URL), }; } -export function getApiClient() { - return createApiClientWithDetails(getApiDetails()); +export function getApiClient(env: ReadOnlyEnv = getEnv()) { + return createApiClientWithDetails(getApiDetails(env)); } export function getApiClientWithExternalAuth( diff --git a/src/environment.ts b/src/environment.ts index e2cfe49e8b..8240ee4518 100644 --- a/src/environment.ts +++ b/src/environment.ts @@ -167,10 +167,7 @@ export enum EnvVar { /** * Gets an environment variable, but throws an error if it is not set. */ -export function getRequiredEnvVar( - env: NodeJS.ProcessEnv, - paramName: string, -): string { +function getRequiredEnvVar(env: NodeJS.ProcessEnv, paramName: string): string { const value = env[paramName]; if (value === undefined || value.length === 0) { throw new Error(`${paramName} environment variable must be set`); @@ -180,6 +177,8 @@ export function getRequiredEnvVar( /** * Get an environment parameter, but throw an error if it is not set. + * + * @deprecated Use `getRequired` of a `ReadOnlyEnv` or `Env` instance instead. */ export function getRequiredEnvParam(paramName: string): string { return getRequiredEnvVar(process.env, paramName); @@ -188,7 +187,7 @@ export function getRequiredEnvParam(paramName: string): string { /** * Gets an environment variable, but returns `undefined` if it is not set or empty. */ -export function getOptionalEnvVarFrom( +function getOptionalEnvVarFrom( env: NodeJS.ProcessEnv, paramName: string, ): string | undefined { @@ -201,31 +200,52 @@ export function getOptionalEnvVarFrom( /** * Get an environment variable, but return `undefined` if it is not set or empty. + * + * @deprecated Use `getOptional` of a `ReadOnlyEnv` or `Env` instance instead. */ export function getOptionalEnvVar(paramName: string): string | undefined { return getOptionalEnvVarFrom(process.env, paramName); } -/** Gets an `Env` instance for `env`, which is `process.env` by default. */ -export function getEnv(env: NodeJS.ProcessEnv = process.env): Env { - return { - getRequired: (name) => getRequiredEnvVar(env, name), - getOptional: (name) => getOptionalEnvVarFrom(env, name), - entries: () => Object.entries(env), - set: (name, value) => { - env[name] = value; - }, - }; -} +/** + * An abstraction around read-only environment variables, to allow abstracting away from `process.env` + * in tests, while clearly signalling in regular code that the consumer of the `ReadOnlyEnv` instance + * will only read from it. + */ +export class ReadOnlyEnv { + constructor(protected readonly vars: Record) {} -/** A wrapper around an environment, to allow abstracting away from `process.env` in tests. */ -export interface Env { /** Tries to get the value for `name` and throws if there isn't one. */ - getRequired(name: string): string; + public getRequired(name: string): string { + return getRequiredEnvVar(this.vars, name); + } + /** Gets the value for `name`, or `undefined` if it isn't set or empty. */ - getOptional(name: string): string | undefined; + public getOptional(name: string): string | undefined { + return getOptionalEnvVarFrom(this.vars, name); + } + /** Gets the entries of the underlying `ProcessEnv`. */ - entries(): Array<[string, string | undefined]>; + public entries(): Array<[string, T]> { + return Object.entries(this.vars); + } +} + +/** + * A wrapper around an environment, to allow abstracting away from `process.env` in tests. + * Use `ReadOnlyEnv` instead if you only plan to read from the environment. + * This type allows writing to the environment. + */ +export class Env< + T extends string | undefined = string | undefined, +> extends ReadOnlyEnv { /** Sets an environment variable. */ - set(name: string, value: string): void; + public set(name: string, value: T): void { + this.vars[name] = value; + } +} + +/** Gets an `Env` instance for `env`, which is `process.env` by default. */ +export function getEnv(env: NodeJS.ProcessEnv = process.env): Env { + return new Env(env); } From be67a7be936375235abfe084bfe0bf4a1bd493f8 Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Thu, 9 Jul 2026 12:41:31 +0100 Subject: [PATCH 05/10] Add `hasChanged` method to `Env` --- lib/entry-points.js | 6 ++++++ src/environment.ts | 8 ++++++++ 2 files changed, 14 insertions(+) diff --git a/lib/entry-points.js b/lib/entry-points.js index c4bf3a3bda..a84f452bec 100644 --- a/lib/entry-points.js +++ b/lib/entry-points.js @@ -141502,9 +141502,15 @@ var ReadOnlyEnv = class { } }; var Env = class extends ReadOnlyEnv { + changed = false; /** Sets an environment variable. */ set(name, value) { this.vars[name] = value; + this.changed = true; + } + /** Gets a value indicating whether `set` was called at least once. */ + hasChanged() { + return this.changed; } }; function getEnv(env = process.env) { diff --git a/src/environment.ts b/src/environment.ts index 8240ee4518..7bda149d55 100644 --- a/src/environment.ts +++ b/src/environment.ts @@ -239,9 +239,17 @@ export class ReadOnlyEnv { export class Env< T extends string | undefined = string | undefined, > extends ReadOnlyEnv { + private changed: boolean = false; + /** Sets an environment variable. */ public set(name: string, value: T): void { this.vars[name] = value; + this.changed = true; + } + + /** Gets a value indicating whether `set` was called at least once. */ + public hasChanged(): boolean { + return this.changed; } } From a440e35edaa7da26a99db2a8962be7a7db10153c Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Thu, 9 Jul 2026 12:48:49 +0100 Subject: [PATCH 06/10] Move `ActionsEnvVars` to `environment.ts` --- src/actions-util.ts | 24 +----------------------- src/api-client.test.ts | 11 +++-------- src/api-client.ts | 8 ++------ src/config/remote-file.test.ts | 2 +- src/config/remote-file.ts | 3 +-- src/environment.ts | 25 +++++++++++++++++++++++++ src/testing-utils.ts | 4 ++-- 7 files changed, 35 insertions(+), 42 deletions(-) diff --git a/src/actions-util.ts b/src/actions-util.ts index 251e804e70..9782bfb5e3 100644 --- a/src/actions-util.ts +++ b/src/actions-util.ts @@ -7,7 +7,7 @@ import * as github from "@actions/github"; import * as io from "@actions/io"; import type { Config } from "./config-utils"; -import { Env, EnvVar } from "./environment"; +import { Env, EnvVar, ActionsEnvVars } from "./environment"; import { Logger } from "./logging"; import { doesDirectoryExist, @@ -22,28 +22,6 @@ import { */ declare const __CODEQL_ACTION_VERSION__: string; -/** - * Enumerates known GitHub Actions environment variables that we expect - * to be set in a GitHub Actions environment. - */ -export enum ActionsEnvVars { - GITHUB_ACTION_REPOSITORY = "GITHUB_ACTION_REPOSITORY", - GITHUB_API_URL = "GITHUB_API_URL", - GITHUB_EVENT_NAME = "GITHUB_EVENT_NAME", - GITHUB_EVENT_PATH = "GITHUB_EVENT_PATH", - GITHUB_JOB = "GITHUB_JOB", - GITHUB_REF = "GITHUB_REF", - GITHUB_REPOSITORY = "GITHUB_REPOSITORY", - GITHUB_RUN_ATTEMPT = "GITHUB_RUN_ATTEMPT", - GITHUB_RUN_ID = "GITHUB_RUN_ID", - GITHUB_SERVER_URL = "GITHUB_SERVER_URL", - GITHUB_SHA = "GITHUB_SHA", - GITHUB_WORKFLOW = "GITHUB_WORKFLOW", - RUNNER_NAME = "RUNNER_NAME", - RUNNER_OS = "RUNNER_OS", - RUNNER_TEMP = "RUNNER_TEMP", -} - /** * Abstracts over GitHub Actions functions so that we do not have to stub * global functions in tests. diff --git a/src/api-client.test.ts b/src/api-client.test.ts index affb5b8946..34a72f14e5 100644 --- a/src/api-client.test.ts +++ b/src/api-client.test.ts @@ -6,6 +6,7 @@ import * as sinon from "sinon"; import * as actionsUtil from "./actions-util"; import * as api from "./api-client"; import { DO_NOT_RETRY_STATUSES } from "./api-client"; +import { ActionsEnvVars } from "./environment"; import { getTestEnv, setupTests } from "./testing-utils"; import * as util from "./util"; @@ -21,14 +22,8 @@ test.serial("getApiClient", async (t) => { pluginStub.returns(githubStub); const env = getTestEnv(); - env.set( - actionsUtil.ActionsEnvVars.GITHUB_SERVER_URL, - "http://github.localhost", - ); - env.set( - actionsUtil.ActionsEnvVars.GITHUB_API_URL, - "http://api.github.localhost", - ); + env.set(ActionsEnvVars.GITHUB_SERVER_URL, "http://github.localhost"); + env.set(ActionsEnvVars.GITHUB_API_URL, "http://api.github.localhost"); sinon.stub(actionsUtil, "getRequiredInput").withArgs("token").returns("xyz"); diff --git a/src/api-client.ts b/src/api-client.ts index a4a80cd485..eafac7ec76 100644 --- a/src/api-client.ts +++ b/src/api-client.ts @@ -2,12 +2,8 @@ import * as core from "@actions/core"; import * as githubUtils from "@actions/github/lib/utils"; import * as retry from "@octokit/plugin-retry"; -import { - ActionsEnvVars, - getActionVersion, - getRequiredInput, -} from "./actions-util"; -import { EnvVar, ReadOnlyEnv, getEnv } from "./environment"; +import { getActionVersion, getRequiredInput } from "./actions-util"; +import { EnvVar, ReadOnlyEnv, ActionsEnvVars, getEnv } from "./environment"; import { Logger } from "./logging"; import { getRepositoryNwo, RepositoryNwo } from "./repository"; import { diff --git a/src/config/remote-file.test.ts b/src/config/remote-file.test.ts index c366370293..ac1492d906 100644 --- a/src/config/remote-file.test.ts +++ b/src/config/remote-file.test.ts @@ -1,7 +1,7 @@ import test from "ava"; import sinon from "sinon"; -import { ActionsEnvVars } from "../actions-util"; +import { ActionsEnvVars } from "../environment"; import * as errors from "../error-messages"; import { Feature } from "../feature-flags"; import { callee, getTestEnv } from "../testing-utils"; diff --git a/src/config/remote-file.ts b/src/config/remote-file.ts index 15990ed23f..15b34af2b6 100644 --- a/src/config/remote-file.ts +++ b/src/config/remote-file.ts @@ -1,6 +1,5 @@ import { ActionState } from "../action-common"; -import { ActionsEnvVars } from "../actions-util"; -import { Env } from "../environment"; +import { Env, ActionsEnvVars } from "../environment"; import * as errorMessages from "../error-messages"; import { Feature } from "../feature-flags"; import { ConfigurationError, Failure, Result, Success } from "../util"; diff --git a/src/environment.ts b/src/environment.ts index 7bda149d55..663a6396d0 100644 --- a/src/environment.ts +++ b/src/environment.ts @@ -164,6 +164,31 @@ export enum EnvVar { RISK_ASSESSMENT_ID = "CODEQL_ACTION_RISK_ASSESSMENT_ID", } +/** + * Enumerates known GitHub Actions environment variables that we expect + * to be set in a GitHub Actions environment. + */ +export enum ActionsEnvVars { + GITHUB_ACTION_REPOSITORY = "GITHUB_ACTION_REPOSITORY", + GITHUB_API_URL = "GITHUB_API_URL", + GITHUB_EVENT_NAME = "GITHUB_EVENT_NAME", + GITHUB_EVENT_PATH = "GITHUB_EVENT_PATH", + GITHUB_JOB = "GITHUB_JOB", + GITHUB_REF = "GITHUB_REF", + GITHUB_REPOSITORY = "GITHUB_REPOSITORY", + GITHUB_RUN_ATTEMPT = "GITHUB_RUN_ATTEMPT", + GITHUB_RUN_ID = "GITHUB_RUN_ID", + GITHUB_SERVER_URL = "GITHUB_SERVER_URL", + GITHUB_SHA = "GITHUB_SHA", + GITHUB_WORKFLOW = "GITHUB_WORKFLOW", + RUNNER_NAME = "RUNNER_NAME", + RUNNER_OS = "RUNNER_OS", + RUNNER_TEMP = "RUNNER_TEMP", +} + +/** A type representing all known environment variables. */ +export type KnownEnvVar = EnvVar | ActionsEnvVars; + /** * Gets an environment variable, but throws an error if it is not set. */ diff --git a/src/testing-utils.ts b/src/testing-utils.ts index c6ff0dbdac..e78d6a616e 100644 --- a/src/testing-utils.ts +++ b/src/testing-utils.ts @@ -11,7 +11,7 @@ import nock from "nock"; import * as sinon from "sinon"; import { ActionState, StateFeature } from "./action-common"; -import { ActionsEnv, ActionsEnvVars, getActionVersion } from "./actions-util"; +import { ActionsEnv, getActionVersion } from "./actions-util"; import { AnalysisKind } from "./analyses"; import * as apiClient from "./api-client"; import { GitHubApiDetails } from "./api-client"; @@ -19,7 +19,7 @@ import { CachingKind } from "./caching-utils"; import * as codeql from "./codeql"; import { Config } from "./config-utils"; import * as defaults from "./defaults.json"; -import { Env } from "./environment"; +import { Env, ActionsEnvVars } from "./environment"; import { CodeQLDefaultVersionInfo, Feature, From 80dcb7ccdf33e958fc80b56bab2efeeef1133ddc Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Thu, 9 Jul 2026 14:31:08 +0100 Subject: [PATCH 07/10] Add `RUNNER_ENVIRONMENT` to `ActionsEnvVars` --- lib/entry-points.js | 4 ++-- src/actions-util.ts | 2 +- src/autobuild.ts | 4 ++-- src/environment.ts | 1 + src/init.test.ts | 3 ++- 5 files changed, 8 insertions(+), 6 deletions(-) diff --git a/lib/entry-points.js b/lib/entry-points.js index a84f452bec..19e45627c7 100644 --- a/lib/entry-points.js +++ b/lib/entry-points.js @@ -145330,7 +145330,7 @@ var getFileType = async (filePath) => { } }; function isSelfHostedRunner(env = getEnv()) { - return env.getOptional("RUNNER_ENVIRONMENT") === "self-hosted"; + return env.getOptional("RUNNER_ENVIRONMENT" /* RUNNER_ENVIRONMENT */) === "self-hosted"; } function isDynamicWorkflow(env = getEnv()) { return getWorkflowEventName(env) === "dynamic"; @@ -151679,7 +151679,7 @@ async function setupCppAutobuild(codeql, logger) { logger ); if (await features.getValue("cpp_dependency_installation_enabled" /* CppDependencyInstallation */, codeql)) { - if (process.env["RUNNER_ENVIRONMENT"] === "self-hosted" && process.env[envVar] !== "true") { + if (process.env["RUNNER_ENVIRONMENT" /* RUNNER_ENVIRONMENT */] === "self-hosted" && process.env[envVar] !== "true") { logger.info( `Disabling ${featureName} as we are on a self-hosted runner.${getWorkflowEventName() !== "dynamic" ? ` To override this, set the ${envVar} environment variable to 'true' in your workflow. See ${"https://docs.github.com/en/actions/learn-github-actions/variables#defining-environment-variables-for-a-single-workflow" /* DEFINE_ENV_VARIABLES */} for more information.` : ""}` ); diff --git a/src/actions-util.ts b/src/actions-util.ts index 9782bfb5e3..12af438fba 100644 --- a/src/actions-util.ts +++ b/src/actions-util.ts @@ -275,7 +275,7 @@ export const getFileType = async (filePath: string): Promise => { }; export function isSelfHostedRunner(env: Env = getEnv()) { - return env.getOptional("RUNNER_ENVIRONMENT") === "self-hosted"; + return env.getOptional(ActionsEnvVars.RUNNER_ENVIRONMENT) === "self-hosted"; } /** Determines whether the workflow trigger is `dynamic`. */ diff --git a/src/autobuild.ts b/src/autobuild.ts index fc4983f4ef..7ec6ba9873 100644 --- a/src/autobuild.ts +++ b/src/autobuild.ts @@ -5,7 +5,7 @@ import { getGitHubVersion } from "./api-client"; import { CodeQL, getCodeQL } from "./codeql"; import * as configUtils from "./config-utils"; import { DocUrl } from "./doc-url"; -import { EnvVar } from "./environment"; +import { ActionsEnvVars, EnvVar } from "./environment"; import { Feature, featureConfig, initFeatures } from "./feature-flags"; import { BuiltInLanguage, Language } from "./languages"; import { Logger } from "./logging"; @@ -126,7 +126,7 @@ export async function setupCppAutobuild(codeql: CodeQL, logger: Logger) { if (await features.getValue(Feature.CppDependencyInstallation, codeql)) { // disable autoinstall on self-hosted runners unless explicitly requested if ( - process.env["RUNNER_ENVIRONMENT"] === "self-hosted" && + process.env[ActionsEnvVars.RUNNER_ENVIRONMENT] === "self-hosted" && process.env[envVar] !== "true" ) { logger.info( diff --git a/src/environment.ts b/src/environment.ts index 663a6396d0..7b007d6524 100644 --- a/src/environment.ts +++ b/src/environment.ts @@ -181,6 +181,7 @@ export enum ActionsEnvVars { GITHUB_SERVER_URL = "GITHUB_SERVER_URL", GITHUB_SHA = "GITHUB_SHA", GITHUB_WORKFLOW = "GITHUB_WORKFLOW", + RUNNER_ENVIRONMENT = "RUNNER_ENVIRONMENT", RUNNER_NAME = "RUNNER_NAME", RUNNER_OS = "RUNNER_OS", RUNNER_TEMP = "RUNNER_TEMP", diff --git a/src/init.test.ts b/src/init.test.ts index 88ad0c9b18..1f0d2c701c 100644 --- a/src/init.test.ts +++ b/src/init.test.ts @@ -8,6 +8,7 @@ import * as sinon from "sinon"; import * as actionsUtil from "./actions-util"; import { createStubCodeQL } from "./codeql"; +import { ActionsEnvVars } from "./environment"; import { Feature } from "./feature-flags"; import { checkPacksForOverlayCompatibility, @@ -84,7 +85,7 @@ for (const { runnerEnv, ErrorConstructor, message } of [ `cleanupDatabaseClusterDirectory throws a ${ErrorConstructor.name} when cleanup fails on ${runnerEnv} runner`, async (t) => { await withTmpDir(async (tmpDir: string) => { - process.env["RUNNER_ENVIRONMENT"] = runnerEnv; + process.env[ActionsEnvVars.RUNNER_ENVIRONMENT] = runnerEnv; const dbLocation = path.resolve(tmpDir, "dbs"); fs.mkdirSync(dbLocation, { recursive: true }); From 3f2a29899f809e77bc387c986dd9410c09ec426e Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Thu, 9 Jul 2026 14:33:36 +0100 Subject: [PATCH 08/10] Default to `getEnv` in `setupBaseActionsVars` --- src/testing-utils.ts | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/testing-utils.ts b/src/testing-utils.ts index e78d6a616e..ec518853f8 100644 --- a/src/testing-utils.ts +++ b/src/testing-utils.ts @@ -335,15 +335,11 @@ export type ActionVarOverrides = Partial< */ export function setupBaseActionsVars( overrides?: ActionVarOverrides, - env?: Env, + env: Env = getEnv(), ) { const vars = { ...DEFAULT_ACTIONS_VARS, ...overrides }; for (const [key, value] of Object.entries(vars)) { - if (env) { - env.set(key, value); - } else { - process.env[key] = value; - } + env.set(key, value); } } From 97b52d19da4858e08d9ab458078e9fdc27f2e15c Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Thu, 9 Jul 2026 14:41:56 +0100 Subject: [PATCH 09/10] Simplify `getTemporaryDirectory` --- lib/entry-points.js | 3 +-- src/actions-util.ts | 7 +++---- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/lib/entry-points.js b/lib/entry-points.js index 19e45627c7..7cbac6160e 100644 --- a/lib/entry-points.js +++ b/lib/entry-points.js @@ -145181,8 +145181,7 @@ var getOptionalInput = function(name) { return value.length > 0 ? value : void 0; }; function getTemporaryDirectory(env = getEnv()) { - const value = env.getOptional("CODEQL_ACTION_TEMP" /* TEMP */); - return value !== void 0 && value !== "" ? value : env.getRequired("RUNNER_TEMP" /* RUNNER_TEMP */); + return env.getOptional("CODEQL_ACTION_TEMP" /* TEMP */) ?? env.getRequired("RUNNER_TEMP" /* RUNNER_TEMP */); } var PR_DIFF_RANGE_JSON_FILENAME = "pr-diff-range.json"; function getDiffRangesJsonFilePath(env = getEnv()) { diff --git a/src/actions-util.ts b/src/actions-util.ts index 12af438fba..52340c06b6 100644 --- a/src/actions-util.ts +++ b/src/actions-util.ts @@ -68,10 +68,9 @@ export const getOptionalInput = function (name: string): string | undefined { * value of `RUNNER_TEMP` otherwise. */ export function getTemporaryDirectory(env: Env = getEnv()): string { - const value = env.getOptional(EnvVar.TEMP); - return value !== undefined && value !== "" - ? value - : env.getRequired(ActionsEnvVars.RUNNER_TEMP); + return ( + env.getOptional(EnvVar.TEMP) ?? env.getRequired(ActionsEnvVars.RUNNER_TEMP) + ); } const PR_DIFF_RANGE_JSON_FILENAME = "pr-diff-range.json"; From 4b164dbc174df46e004e3284afa05e569e8b9134 Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Thu, 9 Jul 2026 14:47:32 +0100 Subject: [PATCH 10/10] Add `CODE_SCANNING_*` vars to `EnvVar` enum --- lib/entry-points.js | 6 ++++-- src/actions-util.ts | 6 ++++-- src/environment.ts | 12 ++++++++++++ src/setup-codeql.test.ts | 5 +++-- 4 files changed, 23 insertions(+), 6 deletions(-) diff --git a/lib/entry-points.js b/lib/entry-points.js index 7cbac6160e..e89711f226 100644 --- a/lib/entry-points.js +++ b/lib/entry-points.js @@ -145425,8 +145425,10 @@ function getPullRequestBranches(env = getEnv()) { head: pullRequest.head.label }; } - const codeScanningRef = env.getOptional("CODE_SCANNING_REF"); - const codeScanningBaseBranch = env.getOptional("CODE_SCANNING_BASE_BRANCH"); + const codeScanningRef = env.getOptional("CODE_SCANNING_REF" /* CODE_SCANNING_REF */); + const codeScanningBaseBranch = env.getOptional( + "CODE_SCANNING_BASE_BRANCH" /* CODE_SCANNING_BASE_BRANCH */ + ); if (codeScanningRef && codeScanningBaseBranch) { return { base: codeScanningBaseBranch, diff --git a/src/actions-util.ts b/src/actions-util.ts index 52340c06b6..5fd1ebc4fe 100644 --- a/src/actions-util.ts +++ b/src/actions-util.ts @@ -429,8 +429,10 @@ export function getPullRequestBranches( // PR analysis under Default Setup does not have the pull_request context, // but it should set CODE_SCANNING_REF and CODE_SCANNING_BASE_BRANCH. - const codeScanningRef = env.getOptional("CODE_SCANNING_REF"); - const codeScanningBaseBranch = env.getOptional("CODE_SCANNING_BASE_BRANCH"); + const codeScanningRef = env.getOptional(EnvVar.CODE_SCANNING_REF); + const codeScanningBaseBranch = env.getOptional( + EnvVar.CODE_SCANNING_BASE_BRANCH, + ); if (codeScanningRef && codeScanningBaseBranch) { return { base: codeScanningBaseBranch, diff --git a/src/environment.ts b/src/environment.ts index 7b007d6524..6ea0e87e4f 100644 --- a/src/environment.ts +++ b/src/environment.ts @@ -17,6 +17,18 @@ export enum EnvVar { */ CLI_VERBOSITY = "CODEQL_VERBOSITY", + /** + * Set by Default Setup to the base branch of the PR being analysed, if analysing a PR. + * This is needed because the `pull_request` context is not available for `dynamic` events. + */ + CODE_SCANNING_BASE_BRANCH = "CODE_SCANNING_BASE_BRANCH", + + /** + * Set by Default Setup to the full ref being analysed, if analysing a PR. + * This is needed because the `pull_request` context is not available for `dynamic` events. + */ + CODE_SCANNING_REF = "CODE_SCANNING_REF", + /** * `PersistedVersionInfo` for the CodeQL CLI, so later Actions steps can reuse it instead of * invoking `codeql version` again. diff --git a/src/setup-codeql.test.ts b/src/setup-codeql.test.ts index 49d4d66aad..f2ba43c101 100644 --- a/src/setup-codeql.test.ts +++ b/src/setup-codeql.test.ts @@ -7,6 +7,7 @@ import * as sinon from "sinon"; import * as actionsUtil from "./actions-util"; import * as api from "./api-client"; +import { EnvVar } from "./environment"; import { Feature } from "./feature-flags"; import { getRunnerLogger } from "./logging"; import { getCacheRestoreKeyPrefix } from "./overlay/caching"; @@ -637,8 +638,8 @@ test.serial( async (t) => { await withTmpDir(async (tmpDir) => { setupActionsVars(tmpDir, tmpDir); - process.env["CODE_SCANNING_REF"] = "refs/heads/feature-branch"; - process.env["CODE_SCANNING_BASE_BRANCH"] = "main"; + process.env[EnvVar.CODE_SCANNING_REF] = "refs/heads/feature-branch"; + process.env[EnvVar.CODE_SCANNING_BASE_BRANCH] = "main"; sinon.stub(api, "getAutomationID").resolves("test/"); const listStub = sinon.stub(api, "listActionsCaches").resolves([