From f57d3726c35cbd0f451b5f330eee2571def792b0 Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Tue, 16 Jun 2026 12:02:08 +0200 Subject: [PATCH 1/5] Add `github-codeql-config-file` property --- lib/entry-points.js | 2 ++ src/feature-flags/properties.test.ts | 6 +++++- src/feature-flags/properties.ts | 4 ++++ 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/lib/entry-points.js b/lib/entry-points.js index c69fa78ff8..1976920196 100644 --- a/lib/entry-points.js +++ b/lib/entry-points.js @@ -150233,6 +150233,7 @@ function getUnknownLanguagesError(languages) { // src/feature-flags/properties.ts var GITHUB_CODEQL_PROPERTY_PREFIX = "github-codeql-"; var RepositoryPropertyName = /* @__PURE__ */ ((RepositoryPropertyName2) => { + RepositoryPropertyName2["CONFIG_FILE"] = "github-codeql-config-file"; RepositoryPropertyName2["DISABLE_OVERLAY"] = "github-codeql-disable-overlay"; RepositoryPropertyName2["EXTRA_QUERIES"] = "github-codeql-extra-queries"; RepositoryPropertyName2["FILE_COVERAGE_ON_PRS"] = "github-codeql-file-coverage-on-prs"; @@ -150251,6 +150252,7 @@ var booleanProperty = { parse: parseBooleanRepositoryProperty }; var repositoryPropertyParsers = { + ["github-codeql-config-file" /* CONFIG_FILE */]: stringProperty, ["github-codeql-disable-overlay" /* DISABLE_OVERLAY */]: booleanProperty, ["github-codeql-extra-queries" /* EXTRA_QUERIES */]: stringProperty, ["github-codeql-file-coverage-on-prs" /* FILE_COVERAGE_ON_PRS */]: booleanProperty diff --git a/src/feature-flags/properties.test.ts b/src/feature-flags/properties.test.ts index 2676c2dcda..66526b1fb2 100644 --- a/src/feature-flags/properties.test.ts +++ b/src/feature-flags/properties.test.ts @@ -77,6 +77,7 @@ test.serial("loadPropertiesFromApi loads known properties", async (t) => { status: 200, url: "", data: [ + { property_name: "github-codeql-config-file", value: "owner/repo" }, { property_name: "github-codeql-extra-queries", value: "+queries" }, { property_name: "unknown-property", value: "something" }, ] satisfies properties.GitHubPropertiesResponse, @@ -87,7 +88,10 @@ test.serial("loadPropertiesFromApi loads known properties", async (t) => { logger, mockRepositoryNwo, ); - t.deepEqual(response, { "github-codeql-extra-queries": "+queries" }); + t.deepEqual(response, { + "github-codeql-config-file": "owner/repo", + "github-codeql-extra-queries": "+queries", + }); }); test.serial("loadPropertiesFromApi parses true boolean property", async (t) => { diff --git a/src/feature-flags/properties.ts b/src/feature-flags/properties.ts index 12ba280bec..e239c71947 100644 --- a/src/feature-flags/properties.ts +++ b/src/feature-flags/properties.ts @@ -10,6 +10,7 @@ export const GITHUB_CODEQL_PROPERTY_PREFIX = "github-codeql-"; * Enumerates repository property names that have some meaning to us. */ export enum RepositoryPropertyName { + CONFIG_FILE = "github-codeql-config-file", DISABLE_OVERLAY = "github-codeql-disable-overlay", EXTRA_QUERIES = "github-codeql-extra-queries", FILE_COVERAGE_ON_PRS = "github-codeql-file-coverage-on-prs", @@ -17,6 +18,7 @@ export enum RepositoryPropertyName { /** Parsed types of the known repository properties. */ export type AllRepositoryProperties = { + [RepositoryPropertyName.CONFIG_FILE]: string; [RepositoryPropertyName.DISABLE_OVERLAY]: boolean; [RepositoryPropertyName.EXTRA_QUERIES]: string; [RepositoryPropertyName.FILE_COVERAGE_ON_PRS]: boolean; @@ -27,6 +29,7 @@ export type RepositoryProperties = Partial; /** Maps known repository properties to the type we expect to get from the API. */ export type RepositoryPropertyApiType = { + [RepositoryPropertyName.CONFIG_FILE]: string; [RepositoryPropertyName.DISABLE_OVERLAY]: string; [RepositoryPropertyName.EXTRA_QUERIES]: string; [RepositoryPropertyName.FILE_COVERAGE_ON_PRS]: string; @@ -74,6 +77,7 @@ const booleanProperty = { const repositoryPropertyParsers: { [K in RepositoryPropertyName]: PropertyInfo; } = { + [RepositoryPropertyName.CONFIG_FILE]: stringProperty, [RepositoryPropertyName.DISABLE_OVERLAY]: booleanProperty, [RepositoryPropertyName.EXTRA_QUERIES]: stringProperty, [RepositoryPropertyName.FILE_COVERAGE_ON_PRS]: booleanProperty, From a0276b7dc4ed407d422fc7c7d896a1f1515d7f8f Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Tue, 16 Jun 2026 12:19:30 +0200 Subject: [PATCH 2/5] Add initial `ActionsEnv` abstraction --- src/actions-util.ts | 15 +++++++++++++++ src/testing-utils.ts | 11 ++++++++++- 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/src/actions-util.ts b/src/actions-util.ts index 592a70ace3..dea22d5c57 100644 --- a/src/actions-util.ts +++ b/src/actions-util.ts @@ -21,6 +21,21 @@ import { */ declare const __CODEQL_ACTION_VERSION__: string; +/** + * Abstracts over GitHub Actions functions so that we do not have to stub + * global functions in tests. + */ +export interface ActionsEnv { + getOptionalInput: (name: string) => string | undefined; +} + +/** + * Gets the real `ActionsEnv` used by production code. + */ +export function getActionsEnv(): ActionsEnv { + return { getOptionalInput }; +} + /** * Wrapper around core.getInput for inputs that always have a value. * Also see getOptionalInput. diff --git a/src/testing-utils.ts b/src/testing-utils.ts index 1702d6835a..2660c21a69 100644 --- a/src/testing-utils.ts +++ b/src/testing-utils.ts @@ -10,7 +10,7 @@ import test, { import nock from "nock"; import * as sinon from "sinon"; -import { 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"; @@ -172,6 +172,15 @@ export function makeMacro( return wrapper; } +/** + * Gets an `ActionsEnv` instance for use in tests. + */ +export function getTestActionsEnv(): ActionsEnv { + return { + getOptionalInput: () => undefined, + }; +} + /** * Default values for environment variables typically set in an Actions * environment. Tests can override individual variables by passing them in the From ae7c3ea645c88403e950046a7f5ba9f8475e6209 Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Tue, 16 Jun 2026 12:21:33 +0200 Subject: [PATCH 3/5] Add `getConfigFileInput` function --- lib/entry-points.js | 11 ++++++++++- src/config/file.test.ts | 26 ++++++++++++++++++++++++++ src/config/file.ts | 8 ++++++++ src/init-action.ts | 5 ++++- 4 files changed, 48 insertions(+), 2 deletions(-) create mode 100644 src/config/file.test.ts create mode 100644 src/config/file.ts diff --git a/lib/entry-points.js b/lib/entry-points.js index 1976920196..b29405b21d 100644 --- a/lib/entry-points.js +++ b/lib/entry-points.js @@ -148162,6 +148162,9 @@ var Failure = class { }; // src/actions-util.ts +function getActionsEnv() { + return { getOptionalInput }; +} var getRequiredInput = function(name) { const value = core3.getInput(name); if (!value) { @@ -158528,6 +158531,11 @@ var github3 = __toESM(require_github()); var io7 = __toESM(require_io()); var semver10 = __toESM(require_semver2()); +// src/config/file.ts +function getConfigFileInput(actions) { + return actions.getOptionalInput("config-file"); +} + // src/workflow.ts var fs26 = __toESM(require("fs")); var path22 = __toESM(require("path")); @@ -158868,6 +158876,7 @@ async function sendCompletedStatusReport2(startedAt, config, configFile, toolsDo } async function run3(startedAt) { const logger = getActionsLogger(); + const actionsEnv = getActionsEnv(); let apiDetails; let config; let configFile; @@ -158906,7 +158915,7 @@ async function run3(startedAt) { logger.info(`Job run UUID is ${jobRunUuid}.`); core20.exportVariable("JOB_RUN_UUID" /* JOB_RUN_UUID */, jobRunUuid); core20.exportVariable("CODEQL_ACTION_INIT_HAS_RUN" /* INIT_ACTION_HAS_RUN */, "true"); - configFile = getOptionalInput("config-file"); + configFile = getConfigFileInput(actionsEnv); sourceRoot = path23.resolve( getRequiredEnvParam("GITHUB_WORKSPACE"), getOptionalInput("source-root") || "" diff --git a/src/config/file.test.ts b/src/config/file.test.ts new file mode 100644 index 0000000000..9a883cc6a8 --- /dev/null +++ b/src/config/file.test.ts @@ -0,0 +1,26 @@ +import test from "ava"; +import sinon from "sinon"; + +import { getTestActionsEnv, setupTests } from "../testing-utils"; + +import { getConfigFileInput } from "./file"; + +setupTests(test); + +test("getConfigFileInput returns undefined by default", async (t) => { + const actionsEnv = getTestActionsEnv(); + const result = getConfigFileInput(actionsEnv); + t.is(result, undefined); +}); + +test("getConfigFileInput returns input value", async (t) => { + const actionsEnv = getTestActionsEnv(); + const testInput = "/some/path"; + sinon + .stub(actionsEnv, "getOptionalInput") + .withArgs("config-file") + .returns(testInput); + + const result = getConfigFileInput(actionsEnv); + t.is(result, testInput); +}); diff --git a/src/config/file.ts b/src/config/file.ts new file mode 100644 index 0000000000..89e0caf6df --- /dev/null +++ b/src/config/file.ts @@ -0,0 +1,8 @@ +import { ActionsEnv } from "../actions-util"; + +/** + * Gets the value that is configured for the configuration file, if any. + */ +export function getConfigFileInput(actions: ActionsEnv): string | undefined { + return actions.getOptionalInput("config-file"); +} diff --git a/src/init-action.ts b/src/init-action.ts index b7593a51cc..b533de1d42 100644 --- a/src/init-action.ts +++ b/src/init-action.ts @@ -9,6 +9,7 @@ import { v4 as uuidV4 } from "uuid"; import { FileCmdNotFoundError, + getActionsEnv, getActionVersion, getFileType, getOptionalInput, @@ -24,6 +25,7 @@ import { shouldRestoreCache, } from "./caching-utils"; import { CodeQL } from "./codeql"; +import { getConfigFileInput } from "./config/file"; import * as configUtils from "./config-utils"; import { DependencyCacheRestoreStatusReport, @@ -207,6 +209,7 @@ async function run(startedAt: Date) { // possible, and only use safe functions outside. const logger = getActionsLogger(); + const actionsEnv = getActionsEnv(); let apiDetails: GitHubApiCombinedDetails; let config: configUtils.Config | undefined; @@ -259,7 +262,7 @@ async function run(startedAt: Date) { core.exportVariable(EnvVar.INIT_ACTION_HAS_RUN, "true"); - configFile = getOptionalInput("config-file"); + configFile = getConfigFileInput(actionsEnv); // path.resolve() respects the intended semantics of source-root. If // source-root is relative, it is relative to the GITHUB_WORKSPACE. If From 82036913c81488b4794375fe22fab316133e88fa Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Tue, 16 Jun 2026 12:28:17 +0200 Subject: [PATCH 4/5] Accept repository property as fallback --- lib/entry-points.js | 8 ++++---- src/config/file.test.ts | 19 +++++++++++++++++-- src/config/file.ts | 14 ++++++++++++-- src/init-action.ts | 4 ++-- 4 files changed, 35 insertions(+), 10 deletions(-) diff --git a/lib/entry-points.js b/lib/entry-points.js index b29405b21d..e2fe5d4a10 100644 --- a/lib/entry-points.js +++ b/lib/entry-points.js @@ -158532,8 +158532,8 @@ var io7 = __toESM(require_io()); var semver10 = __toESM(require_semver2()); // src/config/file.ts -function getConfigFileInput(actions) { - return actions.getOptionalInput("config-file"); +function getConfigFileInput(actions, repositoryProperties) { + return actions.getOptionalInput("config-file") || repositoryProperties["github-codeql-config-file" /* CONFIG_FILE */]; } // src/workflow.ts @@ -158911,11 +158911,12 @@ async function run3(startedAt) { repositoryNwo, logger ); + const repositoryProperties = repositoryPropertiesResult.orElse({}); const jobRunUuid = v4_default(); logger.info(`Job run UUID is ${jobRunUuid}.`); core20.exportVariable("JOB_RUN_UUID" /* JOB_RUN_UUID */, jobRunUuid); core20.exportVariable("CODEQL_ACTION_INIT_HAS_RUN" /* INIT_ACTION_HAS_RUN */, "true"); - configFile = getConfigFileInput(actionsEnv); + configFile = getConfigFileInput(actionsEnv, repositoryProperties); sourceRoot = path23.resolve( getRequiredEnvParam("GITHUB_WORKSPACE"), getOptionalInput("source-root") || "" @@ -158977,7 +158978,6 @@ async function run3(startedAt) { } analysisKinds = await getAnalysisKinds(logger, features); const debugMode = getOptionalInput("debug") === "true" || core20.isDebug(); - const repositoryProperties = repositoryPropertiesResult.orElse({}); const fileCoverageResult = await getFileCoverageInformationEnabled( debugMode, codeql, diff --git a/src/config/file.test.ts b/src/config/file.test.ts index 9a883cc6a8..2eee00c50b 100644 --- a/src/config/file.test.ts +++ b/src/config/file.test.ts @@ -1,6 +1,7 @@ import test from "ava"; import sinon from "sinon"; +import { RepositoryPropertyName } from "../feature-flags/properties"; import { getTestActionsEnv, setupTests } from "../testing-utils"; import { getConfigFileInput } from "./file"; @@ -9,10 +10,14 @@ setupTests(test); test("getConfigFileInput returns undefined by default", async (t) => { const actionsEnv = getTestActionsEnv(); - const result = getConfigFileInput(actionsEnv); + const result = getConfigFileInput(actionsEnv, {}); t.is(result, undefined); }); +const repositoryProperties = { + [RepositoryPropertyName.CONFIG_FILE]: "/path/from/property", +}; + test("getConfigFileInput returns input value", async (t) => { const actionsEnv = getTestActionsEnv(); const testInput = "/some/path"; @@ -21,6 +26,16 @@ test("getConfigFileInput returns input value", async (t) => { .withArgs("config-file") .returns(testInput); - const result = getConfigFileInput(actionsEnv); + // Even though both an input and repository property are configured, + // we prefer the direct input to the Action. + const result = getConfigFileInput(actionsEnv, repositoryProperties); t.is(result, testInput); }); + +test("getConfigFileInput returns repository property value", async (t) => { + const actionsEnv = getTestActionsEnv(); + + // Since there is no direct input, we should use the repository property. + const result = getConfigFileInput(actionsEnv, repositoryProperties); + t.is(result, repositoryProperties[RepositoryPropertyName.CONFIG_FILE]); +}); diff --git a/src/config/file.ts b/src/config/file.ts index 89e0caf6df..0feddd1edb 100644 --- a/src/config/file.ts +++ b/src/config/file.ts @@ -1,8 +1,18 @@ import { ActionsEnv } from "../actions-util"; +import { + RepositoryProperties, + RepositoryPropertyName, +} from "../feature-flags/properties"; /** * Gets the value that is configured for the configuration file, if any. */ -export function getConfigFileInput(actions: ActionsEnv): string | undefined { - return actions.getOptionalInput("config-file"); +export function getConfigFileInput( + actions: ActionsEnv, + repositoryProperties: Partial, +): string | undefined { + return ( + actions.getOptionalInput("config-file") || + repositoryProperties[RepositoryPropertyName.CONFIG_FILE] + ); } diff --git a/src/init-action.ts b/src/init-action.ts index b533de1d42..d8ad6580f5 100644 --- a/src/init-action.ts +++ b/src/init-action.ts @@ -254,6 +254,7 @@ async function run(startedAt: Date) { repositoryNwo, logger, ); + const repositoryProperties = repositoryPropertiesResult.orElse({}); // Create a unique identifier for this run. const jobRunUuid = uuidV4(); @@ -262,7 +263,7 @@ async function run(startedAt: Date) { core.exportVariable(EnvVar.INIT_ACTION_HAS_RUN, "true"); - configFile = getConfigFileInput(actionsEnv); + configFile = getConfigFileInput(actionsEnv, repositoryProperties); // path.resolve() respects the intended semantics of source-root. If // source-root is relative, it is relative to the GITHUB_WORKSPACE. If @@ -353,7 +354,6 @@ async function run(startedAt: Date) { analysisKinds = await getAnalysisKinds(logger, features); const debugMode = getOptionalInput("debug") === "true" || core.isDebug(); - const repositoryProperties = repositoryPropertiesResult.orElse({}); const fileCoverageResult = await getFileCoverageInformationEnabled( debugMode, codeql, From b2cc23e25db2c17285003ae9591cf57f7c276006 Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Tue, 16 Jun 2026 12:36:12 +0200 Subject: [PATCH 5/5] Log input choice --- lib/entry-points.js | 18 +++++++++++++++--- src/config/file.test.ts | 25 +++++++++++++++++++++---- src/config/file.ts | 24 ++++++++++++++++++++---- src/init-action.ts | 2 +- 4 files changed, 57 insertions(+), 12 deletions(-) diff --git a/lib/entry-points.js b/lib/entry-points.js index e2fe5d4a10..ff60cfa202 100644 --- a/lib/entry-points.js +++ b/lib/entry-points.js @@ -158532,8 +158532,20 @@ var io7 = __toESM(require_io()); var semver10 = __toESM(require_semver2()); // src/config/file.ts -function getConfigFileInput(actions, repositoryProperties) { - return actions.getOptionalInput("config-file") || repositoryProperties["github-codeql-config-file" /* CONFIG_FILE */]; +function getConfigFileInput(logger, actions, repositoryProperties) { + const input = actions.getOptionalInput("config-file"); + if (input !== void 0) { + logger.info(`Using configuration file input from workflow: ${input}`); + return input; + } + const propertyValue = repositoryProperties["github-codeql-config-file" /* CONFIG_FILE */]; + if (propertyValue !== void 0) { + logger.info( + `Using configuration file input from repository property: ${propertyValue}` + ); + return propertyValue; + } + return void 0; } // src/workflow.ts @@ -158916,7 +158928,7 @@ async function run3(startedAt) { logger.info(`Job run UUID is ${jobRunUuid}.`); core20.exportVariable("JOB_RUN_UUID" /* JOB_RUN_UUID */, jobRunUuid); core20.exportVariable("CODEQL_ACTION_INIT_HAS_RUN" /* INIT_ACTION_HAS_RUN */, "true"); - configFile = getConfigFileInput(actionsEnv, repositoryProperties); + configFile = getConfigFileInput(logger, actionsEnv, repositoryProperties); sourceRoot = path23.resolve( getRequiredEnvParam("GITHUB_WORKSPACE"), getOptionalInput("source-root") || "" diff --git a/src/config/file.test.ts b/src/config/file.test.ts index 2eee00c50b..c27a3ff5ce 100644 --- a/src/config/file.test.ts +++ b/src/config/file.test.ts @@ -2,15 +2,20 @@ import test from "ava"; import sinon from "sinon"; import { RepositoryPropertyName } from "../feature-flags/properties"; -import { getTestActionsEnv, setupTests } from "../testing-utils"; +import { + getTestActionsEnv, + RecordingLogger, + setupTests, +} from "../testing-utils"; import { getConfigFileInput } from "./file"; setupTests(test); test("getConfigFileInput returns undefined by default", async (t) => { + const logger = new RecordingLogger(); const actionsEnv = getTestActionsEnv(); - const result = getConfigFileInput(actionsEnv, {}); + const result = getConfigFileInput(logger, actionsEnv, {}); t.is(result, undefined); }); @@ -19,6 +24,7 @@ const repositoryProperties = { }; test("getConfigFileInput returns input value", async (t) => { + const logger = new RecordingLogger(); const actionsEnv = getTestActionsEnv(); const testInput = "/some/path"; sinon @@ -28,14 +34,25 @@ test("getConfigFileInput returns input value", async (t) => { // Even though both an input and repository property are configured, // we prefer the direct input to the Action. - const result = getConfigFileInput(actionsEnv, repositoryProperties); + const result = getConfigFileInput(logger, actionsEnv, repositoryProperties); t.is(result, testInput); + + // Check for the expected log message. + t.true(logger.hasMessage("Using configuration file input from workflow")); }); test("getConfigFileInput returns repository property value", async (t) => { + const logger = new RecordingLogger(); const actionsEnv = getTestActionsEnv(); // Since there is no direct input, we should use the repository property. - const result = getConfigFileInput(actionsEnv, repositoryProperties); + const result = getConfigFileInput(logger, actionsEnv, repositoryProperties); t.is(result, repositoryProperties[RepositoryPropertyName.CONFIG_FILE]); + + // Check for the expected log message. + t.true( + logger.hasMessage( + "Using configuration file input from repository property", + ), + ); }); diff --git a/src/config/file.ts b/src/config/file.ts index 0feddd1edb..6197e50cca 100644 --- a/src/config/file.ts +++ b/src/config/file.ts @@ -3,16 +3,32 @@ import { RepositoryProperties, RepositoryPropertyName, } from "../feature-flags/properties"; +import { Logger } from "../logging"; /** * Gets the value that is configured for the configuration file, if any. */ export function getConfigFileInput( + logger: Logger, actions: ActionsEnv, repositoryProperties: Partial, ): string | undefined { - return ( - actions.getOptionalInput("config-file") || - repositoryProperties[RepositoryPropertyName.CONFIG_FILE] - ); + const input = actions.getOptionalInput("config-file"); + + if (input !== undefined) { + logger.info(`Using configuration file input from workflow: ${input}`); + return input; + } + + const propertyValue = + repositoryProperties[RepositoryPropertyName.CONFIG_FILE]; + + if (propertyValue !== undefined) { + logger.info( + `Using configuration file input from repository property: ${propertyValue}`, + ); + return propertyValue; + } + + return undefined; } diff --git a/src/init-action.ts b/src/init-action.ts index d8ad6580f5..9c9b45556b 100644 --- a/src/init-action.ts +++ b/src/init-action.ts @@ -263,7 +263,7 @@ async function run(startedAt: Date) { core.exportVariable(EnvVar.INIT_ACTION_HAS_RUN, "true"); - configFile = getConfigFileInput(actionsEnv, repositoryProperties); + configFile = getConfigFileInput(logger, actionsEnv, repositoryProperties); // path.resolve() respects the intended semantics of source-root. If // source-root is relative, it is relative to the GITHUB_WORKSPACE. If