From 96ca55b1573e6881f1de06ecd308154ed04e122b Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Sat, 20 Sep 2025 13:44:02 +0100 Subject: [PATCH 01/23] Ava: Run all tests in `src/` directory --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 8cffcaa509..8c33d89879 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ "lint": "eslint --report-unused-disable-directives --max-warnings=0 .", "lint-ci": "SARIF_ESLINT_IGNORE_SUPPRESSED=true eslint --report-unused-disable-directives --max-warnings=0 . --format @microsoft/eslint-formatter-sarif --output-file=eslint.sarif", "lint-fix": "eslint --report-unused-disable-directives --max-warnings=0 . --fix", - "test": "npm run transpile && ava src/**.test.ts --serial --verbose", + "test": "npm run transpile && ava src/ --serial --verbose", "test-debug": "npm run test -- --timeout=20m", "transpile": "tsc --build --verbose" }, From 4f9b2f7f065cbd60b389893b55fda99565816340 Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Fri, 19 Sep 2025 13:38:21 +0100 Subject: [PATCH 02/23] Add initial client for repository properties --- src/feature-flags/properties.ts | 84 +++++++++++++++++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100644 src/feature-flags/properties.ts diff --git a/src/feature-flags/properties.ts b/src/feature-flags/properties.ts new file mode 100644 index 0000000000..58ae99ffc3 --- /dev/null +++ b/src/feature-flags/properties.ts @@ -0,0 +1,84 @@ +import { getApiClient } from "../api-client"; +import { Logger } from "../logging"; +import { RepositoryNwo } from "../repository"; + +/** + * Enumerates repository property names that have some meaning to us. + */ +export enum RepositoryPropertyName {} + +/** + * A repository property has a name and a value. + */ +export interface RepositoryProperty { + property_name: string; + value: string; +} + +/** + * The API returns a list of `RepositoryProperty` objects. + */ +type GitHubPropertiesResponse = RepositoryProperty[]; + +/** + * A partial mapping from `RepositoryPropertyName` to values. + */ +export type RepositoryProperties = Partial< + Record +>; + +/** + * Retrieves all known repository properties from the API. + * + * @param logger The logger to use. + * @param repositoryNwo Information about the repository for which to load properties. + * @returns Returns a partial mapping from `RepositoryPropertyName` to values. + */ +export async function loadPropertiesFromApi( + logger: Logger, + repositoryNwo: RepositoryNwo, +): Promise { + try { + const response = await getApiClient().request( + "GET /repos/:owner/:repo/properties/values", + { + owner: repositoryNwo.owner, + repo: repositoryNwo.repo, + }, + ); + const remoteProperties = response.data as GitHubPropertiesResponse; + + if (!Array.isArray(remoteProperties)) { + throw new Error( + `Expected repository properties API to return an array, but got: ${JSON.stringify(response.data)}`, + ); + } + + const knownProperties = new Set(Object.keys(RepositoryPropertyName)); + const properties: RepositoryProperties = {}; + for (const property of remoteProperties) { + if (property.property_name === undefined) { + throw new Error( + `Expected property object to have a 'property_name', but got: ${JSON.stringify(property)}`, + ); + } + + if (knownProperties.has(property.property_name)) { + properties[property.property_name] = property.value; + } + } + + logger.debug("Loaded the following values for the repository properties:"); + for (const [property, value] of Object.entries(properties).sort( + ([nameA], [nameB]) => nameA.localeCompare(nameB), + )) { + logger.debug(` ${property}: ${value}`); + } + + return properties; + } catch (e) { + throw new Error( + `Encountered an error while trying to determine repository properties: ${e}`, + ); + } +} From 3b00d0301993aa0320eea380f3e9d450e2a8428c Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Fri, 19 Sep 2025 13:39:23 +0100 Subject: [PATCH 03/23] Load repository properties and store them in the `Config` --- lib/init-action.js | 54 +++++++++++++++++++++++++++++++++++++++- src/config-utils.test.ts | 6 +++-- src/config-utils.ts | 9 +++++++ src/init-action.ts | 8 ++++++ src/testing-utils.ts | 1 + 5 files changed, 75 insertions(+), 3 deletions(-) diff --git a/lib/init-action.js b/lib/init-action.js index bef6ecd486..0dff011a82 100644 --- a/lib/init-action.js +++ b/lib/init-action.js @@ -87493,6 +87493,7 @@ async function initActionState({ sourceRoot, githubVersion, features, + repositoryProperties, logger }, userConfig) { const analysisKinds = await parseAnalysisKinds(analysisKindsInput); @@ -87547,7 +87548,8 @@ async function initActionState({ dependencyCachingEnabled: getCachingKind(dependencyCachingEnabled), extraQueryExclusions: [], overlayDatabaseMode: "none" /* None */, - useOverlayDatabaseCaching: false + useOverlayDatabaseCaching: false, + repositoryProperties }; } async function downloadCacheWithTime(trapCachingEnabled, codeQL, languages, logger) { @@ -88109,6 +88111,51 @@ function flushDiagnostics(config) { unwrittenDiagnostics = []; } +// src/feature-flags/properties.ts +var RepositoryPropertyName = /* @__PURE__ */ ((RepositoryPropertyName2) => { + return RepositoryPropertyName2; +})(RepositoryPropertyName || {}); +async function loadPropertiesFromApi(logger, repositoryNwo) { + try { + const response = await getApiClient().request( + "GET /repos/:owner/:repo/properties/values", + { + owner: repositoryNwo.owner, + repo: repositoryNwo.repo + } + ); + const remoteProperties = response.data; + if (!Array.isArray(remoteProperties)) { + throw new Error( + `Expected repository properties API to return an array, but got: ${JSON.stringify(response.data)}` + ); + } + const knownProperties = new Set(Object.keys(RepositoryPropertyName)); + const properties = {}; + for (const property of remoteProperties) { + if (property.property_name === void 0) { + throw new Error( + `Expected property object to have a 'property_name', but got: ${JSON.stringify(property)}` + ); + } + if (knownProperties.has(property.property_name)) { + properties[property.property_name] = property.value; + } + } + logger.debug("Loaded the following values for the repository properties:"); + for (const [property, value] of Object.entries(properties).sort( + ([nameA], [nameB]) => nameA.localeCompare(nameB) + )) { + logger.debug(` ${property}: ${value}`); + } + return properties; + } catch (e) { + throw new Error( + `Encountered an error while trying to determine repository properties: ${e}` + ); + } +} + // src/init.ts var fs15 = __toESM(require("fs")); var path17 = __toESM(require("path")); @@ -90384,6 +90431,10 @@ async function run() { getTemporaryDirectory(), logger ); + const repositoryProperties = await loadPropertiesFromApi( + logger, + repositoryNwo + ); const jobRunUuid = v4_default(); logger.info(`Job run UUID is ${jobRunUuid}.`); core13.exportVariable("JOB_RUN_UUID" /* JOB_RUN_UUID */, jobRunUuid); @@ -90483,6 +90534,7 @@ async function run() { githubVersion: gitHubVersion, apiDetails, features, + repositoryProperties, logger }); await checkInstallPython311(config.languages, codeql); diff --git a/src/config-utils.test.ts b/src/config-utils.test.ts index 8938adf845..1df142f118 100644 --- a/src/config-utils.test.ts +++ b/src/config-utils.test.ts @@ -82,11 +82,11 @@ function createTestInitConfigInputs( externalRepoAuth: "token", url: "https://github.example.com", apiURL: undefined, - registriesAuthTokens: undefined, }, features: createFeatures([]), + repositoryProperties: {}, logger: getRunnerLogger(true), - }, + } satisfies configUtils.InitConfigInputs, overrides, ); } @@ -223,6 +223,7 @@ test("load code quality config", async (t) => { extraQueryExclusions: [], overlayDatabaseMode: OverlayDatabaseMode.None, useOverlayDatabaseCaching: false, + repositoryProperties: {}, }; t.deepEqual(config, expectedConfig); @@ -461,6 +462,7 @@ test("load non-empty input", async (t) => { extraQueryExclusions: [], overlayDatabaseMode: OverlayDatabaseMode.None, useOverlayDatabaseCaching: false, + repositoryProperties: {}, }; const languagesInput = "javascript"; diff --git a/src/config-utils.ts b/src/config-utils.ts index 1915102899..7a5205c673 100644 --- a/src/config-utils.ts +++ b/src/config-utils.ts @@ -25,6 +25,7 @@ import { import { shouldPerformDiffInformedAnalysis } from "./diff-informed-analysis-utils"; import * as errorMessages from "./error-messages"; import { Feature, FeatureEnablement } from "./feature-flags"; +import { RepositoryProperties } from "./feature-flags/properties"; import { getGitRoot, isAnalyzingDefaultBranch } from "./git-utils"; import { KnownLanguage, Language } from "./languages"; import { Logger } from "./logging"; @@ -167,6 +168,11 @@ export interface Config { * `OverlayBase`. */ useOverlayDatabaseCaching: boolean; + + /** + * A partial mapping from repository properties that affect us to their values. + */ + repositoryProperties: RepositoryProperties; } export async function getSupportedLanguageMap( @@ -389,6 +395,7 @@ export interface InitConfigInputs { githubVersion: GitHubVersion; apiDetails: api.GitHubApiCombinedDetails; features: FeatureEnablement; + repositoryProperties: RepositoryProperties; logger: Logger; } @@ -416,6 +423,7 @@ export async function initActionState( sourceRoot, githubVersion, features, + repositoryProperties, logger, }: InitConfigInputs, userConfig: UserConfig, @@ -488,6 +496,7 @@ export async function initActionState( extraQueryExclusions: [], overlayDatabaseMode: OverlayDatabaseMode.None, useOverlayDatabaseCaching: false, + repositoryProperties, }; } diff --git a/src/init-action.ts b/src/init-action.ts index 508d17333b..eeb14c78b2 100644 --- a/src/init-action.ts +++ b/src/init-action.ts @@ -32,6 +32,7 @@ import { } from "./diagnostics"; import { EnvVar } from "./environment"; import { Feature, Features } from "./feature-flags"; +import { loadPropertiesFromApi } from "./feature-flags/properties"; import { checkInstallPython311, checkPacksForOverlayCompatibility, @@ -196,6 +197,12 @@ async function run() { logger, ); + // Fetch the values of known repository properties that affect us. + const repositoryProperties = await loadPropertiesFromApi( + logger, + repositoryNwo, + ); + const jobRunUuid = uuidV4(); logger.info(`Job run UUID is ${jobRunUuid}.`); core.exportVariable(EnvVar.JOB_RUN_UUID, jobRunUuid); @@ -317,6 +324,7 @@ async function run() { githubVersion: gitHubVersion, apiDetails, features, + repositoryProperties, logger, }); diff --git a/src/testing-utils.ts b/src/testing-utils.ts index c930d5350c..ea3929131c 100644 --- a/src/testing-utils.ts +++ b/src/testing-utils.ts @@ -378,6 +378,7 @@ export function createTestConfig(overrides: Partial): Config { extraQueryExclusions: [], overlayDatabaseMode: OverlayDatabaseMode.None, useOverlayDatabaseCaching: false, + repositoryProperties: {}, } satisfies Config, overrides, ); From 6150aff57f6d614836ab25b6ec6840288c6a6a78 Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Fri, 19 Sep 2025 17:46:50 +0100 Subject: [PATCH 04/23] Add and use `QuerySpec` type --- src/config/db-config.ts | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/config/db-config.ts b/src/config/db-config.ts index 95138460f8..4524b0cdab 100644 --- a/src/config/db-config.ts +++ b/src/config/db-config.ts @@ -16,16 +16,18 @@ export interface IncludeQueryFilter { export type QueryFilter = ExcludeQueryFilter | IncludeQueryFilter; +export interface QuerySpec { + name?: string; + uses: string; +} + /** * Format of the config file supplied by the user. */ export interface UserConfig { name?: string; "disable-default-queries"?: boolean; - queries?: Array<{ - name?: string; - uses: string; - }>; + queries?: QuerySpec[]; "paths-ignore"?: string[]; paths?: string[]; @@ -58,7 +60,7 @@ export interface AugmentationProperties { /** * The queries input from the `with` block of the action declaration */ - queriesInput?: Array<{ uses: string }>; + queriesInput?: QuerySpec[]; /** * Whether or not the packs input combines with the packs in the config. From ed216a06d2e90830a2a373e8dd0ca73882e6bffb Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Sat, 20 Sep 2025 13:02:43 +0100 Subject: [PATCH 05/23] Include queries from repo properties in `AugmentationProperties` --- lib/analyze-action-post.js | 2 +- lib/analyze-action.js | 2 +- lib/autobuild-action.js | 2 +- lib/init-action-post.js | 2 +- lib/init-action.js | 105 ++++++++++++++++-------------- lib/resolve-environment-action.js | 2 +- lib/start-proxy-action-post.js | 2 +- lib/start-proxy-action.js | 2 +- lib/upload-lib.js | 2 +- lib/upload-sarif-action-post.js | 2 +- lib/upload-sarif-action.js | 2 +- src/config-utils.ts | 1 + src/config/db-config.test.ts | 15 +++++ src/config/db-config.ts | 36 +++++++++- src/feature-flags/properties.ts | 4 +- 15 files changed, 121 insertions(+), 60 deletions(-) diff --git a/lib/analyze-action-post.js b/lib/analyze-action-post.js index 927d8150b5..f2a5fc7b76 100644 --- a/lib/analyze-action-post.js +++ b/lib/analyze-action-post.js @@ -26447,7 +26447,7 @@ var require_package = __commonJS({ lint: "eslint --report-unused-disable-directives --max-warnings=0 .", "lint-ci": "SARIF_ESLINT_IGNORE_SUPPRESSED=true eslint --report-unused-disable-directives --max-warnings=0 . --format @microsoft/eslint-formatter-sarif --output-file=eslint.sarif", "lint-fix": "eslint --report-unused-disable-directives --max-warnings=0 . --fix", - test: "npm run transpile && ava src/**.test.ts --serial --verbose", + test: "npm run transpile && ava src/ --serial --verbose", "test-debug": "npm run test -- --timeout=20m", transpile: "tsc --build --verbose" }, diff --git a/lib/analyze-action.js b/lib/analyze-action.js index 0424733363..21150349ba 100644 --- a/lib/analyze-action.js +++ b/lib/analyze-action.js @@ -32296,7 +32296,7 @@ var require_package = __commonJS({ lint: "eslint --report-unused-disable-directives --max-warnings=0 .", "lint-ci": "SARIF_ESLINT_IGNORE_SUPPRESSED=true eslint --report-unused-disable-directives --max-warnings=0 . --format @microsoft/eslint-formatter-sarif --output-file=eslint.sarif", "lint-fix": "eslint --report-unused-disable-directives --max-warnings=0 . --fix", - test: "npm run transpile && ava src/**.test.ts --serial --verbose", + test: "npm run transpile && ava src/ --serial --verbose", "test-debug": "npm run test -- --timeout=20m", transpile: "tsc --build --verbose" }, diff --git a/lib/autobuild-action.js b/lib/autobuild-action.js index e03abc4131..d442a65d07 100644 --- a/lib/autobuild-action.js +++ b/lib/autobuild-action.js @@ -26447,7 +26447,7 @@ var require_package = __commonJS({ lint: "eslint --report-unused-disable-directives --max-warnings=0 .", "lint-ci": "SARIF_ESLINT_IGNORE_SUPPRESSED=true eslint --report-unused-disable-directives --max-warnings=0 . --format @microsoft/eslint-formatter-sarif --output-file=eslint.sarif", "lint-fix": "eslint --report-unused-disable-directives --max-warnings=0 . --fix", - test: "npm run transpile && ava src/**.test.ts --serial --verbose", + test: "npm run transpile && ava src/ --serial --verbose", "test-debug": "npm run test -- --timeout=20m", transpile: "tsc --build --verbose" }, diff --git a/lib/init-action-post.js b/lib/init-action-post.js index b0f821950b..b1ae225861 100644 --- a/lib/init-action-post.js +++ b/lib/init-action-post.js @@ -32296,7 +32296,7 @@ var require_package = __commonJS({ lint: "eslint --report-unused-disable-directives --max-warnings=0 .", "lint-ci": "SARIF_ESLINT_IGNORE_SUPPRESSED=true eslint --report-unused-disable-directives --max-warnings=0 . --format @microsoft/eslint-formatter-sarif --output-file=eslint.sarif", "lint-fix": "eslint --report-unused-disable-directives --max-warnings=0 . --fix", - test: "npm run transpile && ava src/**.test.ts --serial --verbose", + test: "npm run transpile && ava src/ --serial --verbose", "test-debug": "npm run test -- --timeout=20m", transpile: "tsc --build --verbose" }, diff --git a/lib/init-action.js b/lib/init-action.js index 0dff011a82..69082d1471 100644 --- a/lib/init-action.js +++ b/lib/init-action.js @@ -32296,7 +32296,7 @@ var require_package = __commonJS({ lint: "eslint --report-unused-disable-directives --max-warnings=0 .", "lint-ci": "SARIF_ESLINT_IGNORE_SUPPRESSED=true eslint --report-unused-disable-directives --max-warnings=0 . --format @microsoft/eslint-formatter-sarif --output-file=eslint.sarif", "lint-fix": "eslint --report-unused-disable-directives --max-warnings=0 . --fix", - test: "npm run transpile && ava src/**.test.ts --serial --verbose", + test: "npm run transpile && ava src/ --serial --verbose", "test-debug": "npm run test -- --timeout=20m", transpile: "tsc --build --verbose" }, @@ -86244,6 +86244,52 @@ function getUnknownLanguagesError(languages) { return `Did not recognize the following languages: ${languages.join(", ")}`; } +// src/feature-flags/properties.ts +var RepositoryPropertyName = /* @__PURE__ */ ((RepositoryPropertyName2) => { + RepositoryPropertyName2["EXTRA_QUERIES"] = "github-codeql-extra-queries"; + return RepositoryPropertyName2; +})(RepositoryPropertyName || {}); +async function loadPropertiesFromApi(logger, repositoryNwo) { + try { + const response = await getApiClient().request( + "GET /repos/:owner/:repo/properties/values", + { + owner: repositoryNwo.owner, + repo: repositoryNwo.repo + } + ); + const remoteProperties = response.data; + if (!Array.isArray(remoteProperties)) { + throw new Error( + `Expected repository properties API to return an array, but got: ${JSON.stringify(response.data)}` + ); + } + const knownProperties = new Set(Object.keys(RepositoryPropertyName)); + const properties = {}; + for (const property of remoteProperties) { + if (property.property_name === void 0) { + throw new Error( + `Expected property object to have a 'property_name', but got: ${JSON.stringify(property)}` + ); + } + if (knownProperties.has(property.property_name)) { + properties[property.property_name] = property.value; + } + } + logger.debug("Loaded the following values for the repository properties:"); + for (const [property, value] of Object.entries(properties).sort( + ([nameA], [nameB]) => nameA.localeCompare(nameB) + )) { + logger.debug(` ${property}: ${value}`); + } + return properties; + } catch (e) { + throw new Error( + `Encountered an error while trying to determine repository properties: ${e}` + ); + } +} + // src/config/db-config.ts function shouldCombine(inputValue) { return !!inputValue?.trim().startsWith("+"); @@ -86336,7 +86382,7 @@ function parsePacksFromInput(rawPacksInput, languages, packsInputCombines) { }, []) }; } -async function calculateAugmentation(rawPacksInput, rawQueriesInput, languages) { +async function calculateAugmentation(rawPacksInput, rawQueriesInput, repositoryProperties, languages) { const packsInputCombines = shouldCombine(rawPacksInput); const packsInput = parsePacksFromInput( rawPacksInput, @@ -86348,11 +86394,18 @@ async function calculateAugmentation(rawPacksInput, rawQueriesInput, languages) rawQueriesInput, queriesInputCombines ); + const repoExtraQueries = repositoryProperties["github-codeql-extra-queries" /* EXTRA_QUERIES */]; + const repoExtraQueriesCombines = shouldCombine(repoExtraQueries); + const repoPropertyQueries = { + combines: repoExtraQueriesCombines, + input: parseQueriesFromInput(repoExtraQueries, repoExtraQueriesCombines) + }; return { packsInputCombines, packsInput: packsInput?.[languages[0]], queriesInput, - queriesInputCombines + queriesInputCombines, + repoPropertyQueries }; } function parseQueriesFromInput(rawQueriesInput, queriesInputCombines) { @@ -87517,6 +87570,7 @@ async function initActionState({ const augmentationProperties = await calculateAugmentation( packsInput, queriesInput, + repositoryProperties, languages ); const { trapCaches, trapCacheDownloadTime } = await downloadCacheWithTime( @@ -88111,51 +88165,6 @@ function flushDiagnostics(config) { unwrittenDiagnostics = []; } -// src/feature-flags/properties.ts -var RepositoryPropertyName = /* @__PURE__ */ ((RepositoryPropertyName2) => { - return RepositoryPropertyName2; -})(RepositoryPropertyName || {}); -async function loadPropertiesFromApi(logger, repositoryNwo) { - try { - const response = await getApiClient().request( - "GET /repos/:owner/:repo/properties/values", - { - owner: repositoryNwo.owner, - repo: repositoryNwo.repo - } - ); - const remoteProperties = response.data; - if (!Array.isArray(remoteProperties)) { - throw new Error( - `Expected repository properties API to return an array, but got: ${JSON.stringify(response.data)}` - ); - } - const knownProperties = new Set(Object.keys(RepositoryPropertyName)); - const properties = {}; - for (const property of remoteProperties) { - if (property.property_name === void 0) { - throw new Error( - `Expected property object to have a 'property_name', but got: ${JSON.stringify(property)}` - ); - } - if (knownProperties.has(property.property_name)) { - properties[property.property_name] = property.value; - } - } - logger.debug("Loaded the following values for the repository properties:"); - for (const [property, value] of Object.entries(properties).sort( - ([nameA], [nameB]) => nameA.localeCompare(nameB) - )) { - logger.debug(` ${property}: ${value}`); - } - return properties; - } catch (e) { - throw new Error( - `Encountered an error while trying to determine repository properties: ${e}` - ); - } -} - // src/init.ts var fs15 = __toESM(require("fs")); var path17 = __toESM(require("path")); diff --git a/lib/resolve-environment-action.js b/lib/resolve-environment-action.js index 731fac08e9..447d12c654 100644 --- a/lib/resolve-environment-action.js +++ b/lib/resolve-environment-action.js @@ -26447,7 +26447,7 @@ var require_package = __commonJS({ lint: "eslint --report-unused-disable-directives --max-warnings=0 .", "lint-ci": "SARIF_ESLINT_IGNORE_SUPPRESSED=true eslint --report-unused-disable-directives --max-warnings=0 . --format @microsoft/eslint-formatter-sarif --output-file=eslint.sarif", "lint-fix": "eslint --report-unused-disable-directives --max-warnings=0 . --fix", - test: "npm run transpile && ava src/**.test.ts --serial --verbose", + test: "npm run transpile && ava src/ --serial --verbose", "test-debug": "npm run test -- --timeout=20m", transpile: "tsc --build --verbose" }, diff --git a/lib/start-proxy-action-post.js b/lib/start-proxy-action-post.js index 90304233b0..adcfb47e4d 100644 --- a/lib/start-proxy-action-post.js +++ b/lib/start-proxy-action-post.js @@ -26447,7 +26447,7 @@ var require_package = __commonJS({ lint: "eslint --report-unused-disable-directives --max-warnings=0 .", "lint-ci": "SARIF_ESLINT_IGNORE_SUPPRESSED=true eslint --report-unused-disable-directives --max-warnings=0 . --format @microsoft/eslint-formatter-sarif --output-file=eslint.sarif", "lint-fix": "eslint --report-unused-disable-directives --max-warnings=0 . --fix", - test: "npm run transpile && ava src/**.test.ts --serial --verbose", + test: "npm run transpile && ava src/ --serial --verbose", "test-debug": "npm run test -- --timeout=20m", transpile: "tsc --build --verbose" }, diff --git a/lib/start-proxy-action.js b/lib/start-proxy-action.js index 474b6d820d..7a0475a3a1 100644 --- a/lib/start-proxy-action.js +++ b/lib/start-proxy-action.js @@ -44975,7 +44975,7 @@ var require_package = __commonJS({ lint: "eslint --report-unused-disable-directives --max-warnings=0 .", "lint-ci": "SARIF_ESLINT_IGNORE_SUPPRESSED=true eslint --report-unused-disable-directives --max-warnings=0 . --format @microsoft/eslint-formatter-sarif --output-file=eslint.sarif", "lint-fix": "eslint --report-unused-disable-directives --max-warnings=0 . --fix", - test: "npm run transpile && ava src/**.test.ts --serial --verbose", + test: "npm run transpile && ava src/ --serial --verbose", "test-debug": "npm run test -- --timeout=20m", transpile: "tsc --build --verbose" }, diff --git a/lib/upload-lib.js b/lib/upload-lib.js index 4c57a9ddbe..fe2660b72e 100644 --- a/lib/upload-lib.js +++ b/lib/upload-lib.js @@ -33593,7 +33593,7 @@ var require_package = __commonJS({ lint: "eslint --report-unused-disable-directives --max-warnings=0 .", "lint-ci": "SARIF_ESLINT_IGNORE_SUPPRESSED=true eslint --report-unused-disable-directives --max-warnings=0 . --format @microsoft/eslint-formatter-sarif --output-file=eslint.sarif", "lint-fix": "eslint --report-unused-disable-directives --max-warnings=0 . --fix", - test: "npm run transpile && ava src/**.test.ts --serial --verbose", + test: "npm run transpile && ava src/ --serial --verbose", "test-debug": "npm run test -- --timeout=20m", transpile: "tsc --build --verbose" }, diff --git a/lib/upload-sarif-action-post.js b/lib/upload-sarif-action-post.js index 0a92128f73..65af1249c2 100644 --- a/lib/upload-sarif-action-post.js +++ b/lib/upload-sarif-action-post.js @@ -26447,7 +26447,7 @@ var require_package = __commonJS({ lint: "eslint --report-unused-disable-directives --max-warnings=0 .", "lint-ci": "SARIF_ESLINT_IGNORE_SUPPRESSED=true eslint --report-unused-disable-directives --max-warnings=0 . --format @microsoft/eslint-formatter-sarif --output-file=eslint.sarif", "lint-fix": "eslint --report-unused-disable-directives --max-warnings=0 . --fix", - test: "npm run transpile && ava src/**.test.ts --serial --verbose", + test: "npm run transpile && ava src/ --serial --verbose", "test-debug": "npm run test -- --timeout=20m", transpile: "tsc --build --verbose" }, diff --git a/lib/upload-sarif-action.js b/lib/upload-sarif-action.js index a1c04da31a..f4a55884c0 100644 --- a/lib/upload-sarif-action.js +++ b/lib/upload-sarif-action.js @@ -32296,7 +32296,7 @@ var require_package = __commonJS({ lint: "eslint --report-unused-disable-directives --max-warnings=0 .", "lint-ci": "SARIF_ESLINT_IGNORE_SUPPRESSED=true eslint --report-unused-disable-directives --max-warnings=0 . --format @microsoft/eslint-formatter-sarif --output-file=eslint.sarif", "lint-fix": "eslint --report-unused-disable-directives --max-warnings=0 . --fix", - test: "npm run transpile && ava src/**.test.ts --serial --verbose", + test: "npm run transpile && ava src/ --serial --verbose", "test-debug": "npm run test -- --timeout=20m", transpile: "tsc --build --verbose" }, diff --git a/src/config-utils.ts b/src/config-utils.ts index 7a5205c673..d20cf3bae5 100644 --- a/src/config-utils.ts +++ b/src/config-utils.ts @@ -459,6 +459,7 @@ export async function initActionState( const augmentationProperties = await calculateAugmentation( packsInput, queriesInput, + repositoryProperties, languages, ); diff --git a/src/config/db-config.test.ts b/src/config/db-config.test.ts index 78750a29ad..a5778088f3 100644 --- a/src/config/db-config.test.ts +++ b/src/config/db-config.test.ts @@ -1,5 +1,6 @@ import test, { ExecutionContext } from "ava"; +import { RepositoryProperties } from "../feature-flags/properties"; import { KnownLanguage, Language } from "../languages"; import { prettyPrintPack } from "../util"; @@ -190,11 +191,13 @@ const calculateAugmentationMacro = test.macro({ rawPacksInput: string | undefined, rawQueriesInput: string | undefined, languages: Language[], + repositoryProperties: RepositoryProperties, expectedAugmentationProperties: dbConfig.AugmentationProperties, ) => { const actualAugmentationProperties = await dbConfig.calculateAugmentation( rawPacksInput, rawQueriesInput, + repositoryProperties, languages, ); t.deepEqual(actualAugmentationProperties, expectedAugmentationProperties); @@ -208,6 +211,7 @@ test( undefined, undefined, [KnownLanguage.javascript], + {}, { ...dbConfig.defaultAugmentationProperties, }, @@ -219,6 +223,7 @@ test( undefined, " a, b , c, d", [KnownLanguage.javascript], + {}, { ...dbConfig.defaultAugmentationProperties, queriesInput: [{ uses: "a" }, { uses: "b" }, { uses: "c" }, { uses: "d" }], @@ -231,6 +236,7 @@ test( undefined, " + a, b , c, d ", [KnownLanguage.javascript], + {}, { ...dbConfig.defaultAugmentationProperties, queriesInputCombines: true, @@ -244,6 +250,7 @@ test( " codeql/a , codeql/b , codeql/c , codeql/d ", undefined, [KnownLanguage.javascript], + {}, { ...dbConfig.defaultAugmentationProperties, packsInput: ["codeql/a", "codeql/b", "codeql/c", "codeql/d"], @@ -256,6 +263,7 @@ test( " + codeql/a, codeql/b, codeql/c, codeql/d", undefined, [KnownLanguage.javascript], + {}, { ...dbConfig.defaultAugmentationProperties, packsInputCombines: true, @@ -270,6 +278,7 @@ const calculateAugmentationErrorMacro = test.macro({ rawPacksInput: string | undefined, rawQueriesInput: string | undefined, languages: Language[], + repositoryProperties: RepositoryProperties, expectedError: RegExp | string, ) => { await t.throwsAsync( @@ -277,6 +286,7 @@ const calculateAugmentationErrorMacro = test.macro({ dbConfig.calculateAugmentation( rawPacksInput, rawQueriesInput, + repositoryProperties, languages, ), { message: expectedError }, @@ -291,6 +301,7 @@ test( undefined, " + ", [KnownLanguage.javascript], + {}, /The workflow property "queries" is invalid/, ); @@ -300,6 +311,7 @@ test( " + ", undefined, [KnownLanguage.javascript], + {}, /The workflow property "packs" is invalid/, ); @@ -309,6 +321,7 @@ test( " + a/b, c/d ", undefined, [KnownLanguage.javascript, KnownLanguage.java], + {}, /Cannot specify a 'packs' input in a multi-language analysis/, ); @@ -318,6 +331,7 @@ test( " + a/b, c/d ", undefined, [], + {}, /No languages specified/, ); @@ -327,5 +341,6 @@ test( " a-pack-without-a-scope ", undefined, [KnownLanguage.javascript], + {}, /"a-pack-without-a-scope" is not a valid pack/, ); diff --git a/src/config/db-config.ts b/src/config/db-config.ts index 4524b0cdab..00f6eaa0c4 100644 --- a/src/config/db-config.ts +++ b/src/config/db-config.ts @@ -3,6 +3,10 @@ import * as path from "path"; import * as semver from "semver"; import * as errorMessages from "../error-messages"; +import { + RepositoryProperties, + RepositoryPropertyName, +} from "../feature-flags/properties"; import { Language } from "../languages"; import { cloneObject, ConfigurationError, prettyPrintPack } from "../util"; @@ -41,6 +45,17 @@ export interface UserConfig { "query-filters"?: QueryFilter[]; } +/** + * Represents additional configuration data from a source other than + * a configuration file. + */ +interface Augmentation { + /** Whether or not the `input` combines with data in the base config. */ + combines: boolean; + /** The additional input data. */ + input?: T; +} + /** * Describes how to augment the user config with inputs from the action. * @@ -71,6 +86,11 @@ export interface AugmentationProperties { * The packs input from the `with` block of the action declaration */ packsInput?: string[]; + + /** + * Extra queries from the corresponding repository property. + */ + repoPropertyQueries: Augmentation; } /** @@ -82,6 +102,10 @@ export const defaultAugmentationProperties: AugmentationProperties = { packsInputCombines: false, packsInput: undefined, queriesInput: undefined, + repoPropertyQueries: { + combines: false, + input: undefined, + }, }; /** @@ -256,6 +280,7 @@ export function parsePacksFromInput( * * @param rawPacksInput The packs input from the action configuration. * @param rawQueriesInput The queries input from the action configuration. + * @param repositoryProperties The dictionary of repository properties. * @param languages The languages that the config file is for. If the packs input * is non-empty, then there must be exactly one language. Otherwise, an * error is thrown. @@ -265,10 +290,10 @@ export function parsePacksFromInput( * @throws An error if the packs input is non-empty and the languages input does * not have exactly one language. */ -// exported for testing. export async function calculateAugmentation( rawPacksInput: string | undefined, rawQueriesInput: string | undefined, + repositoryProperties: RepositoryProperties, languages: Language[], ): Promise { const packsInputCombines = shouldCombine(rawPacksInput); @@ -283,11 +308,20 @@ export async function calculateAugmentation( queriesInputCombines, ); + const repoExtraQueries = + repositoryProperties[RepositoryPropertyName.EXTRA_QUERIES]; + const repoExtraQueriesCombines = shouldCombine(repoExtraQueries); + const repoPropertyQueries = { + combines: repoExtraQueriesCombines, + input: parseQueriesFromInput(repoExtraQueries, repoExtraQueriesCombines), + }; + return { packsInputCombines, packsInput: packsInput?.[languages[0]], queriesInput, queriesInputCombines, + repoPropertyQueries, }; } diff --git a/src/feature-flags/properties.ts b/src/feature-flags/properties.ts index 58ae99ffc3..fc0905e557 100644 --- a/src/feature-flags/properties.ts +++ b/src/feature-flags/properties.ts @@ -5,7 +5,9 @@ import { RepositoryNwo } from "../repository"; /** * Enumerates repository property names that have some meaning to us. */ -export enum RepositoryPropertyName {} +export enum RepositoryPropertyName { + EXTRA_QUERIES = "github-codeql-extra-queries", +} /** * A repository property has a name and a value. From 781a65ae3251dc2b8a3cc53962295de46828bb31 Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Sat, 20 Sep 2025 14:01:01 +0100 Subject: [PATCH 06/23] Use appropriate error message in `parseQueriesFromInput` for repo property input --- lib/init-action.js | 22 ++++++++++++++++++++-- src/config/db-config.ts | 15 ++++++++++++++- src/error-messages.ts | 13 +++++++++++++ 3 files changed, 47 insertions(+), 3 deletions(-) diff --git a/lib/init-action.js b/lib/init-action.js index 69082d1471..bef956e4a3 100644 --- a/lib/init-action.js +++ b/lib/init-action.js @@ -86223,6 +86223,9 @@ function getConfigFileFormatInvalidMessage(configFile) { function getConfigFileDirectoryGivenMessage(configFile) { return `The configuration file "${configFile}" looks like a directory, not a file`; } +function getEmptyCombinesError() { + return `A '+' was used to specify that you want to add extra arguments to the configuration, but no extra arguments were specified. Please either remove the '+' or specify some extra arguments.`; +} function getConfigFilePropertyError(configFile, property, error2) { if (configFile === void 0) { return `The workflow property "${property}" is invalid: ${error2}`; @@ -86230,6 +86233,9 @@ function getConfigFilePropertyError(configFile, property, error2) { return `The configuration file "${configFile}" is invalid: property "${property}" ${error2}`; } } +function getRepoPropertyError(propertyName, error2) { + return `The repository property "${propertyName}" is invalid: ${error2}`; +} function getPacksStrInvalid(packStr, configFile) { return configFile ? getConfigFilePropertyError( configFile, @@ -86398,7 +86404,16 @@ async function calculateAugmentation(rawPacksInput, rawQueriesInput, repositoryP const repoExtraQueriesCombines = shouldCombine(repoExtraQueries); const repoPropertyQueries = { combines: repoExtraQueriesCombines, - input: parseQueriesFromInput(repoExtraQueries, repoExtraQueriesCombines) + input: parseQueriesFromInput( + repoExtraQueries, + repoExtraQueriesCombines, + new ConfigurationError( + getRepoPropertyError( + "github-codeql-extra-queries" /* EXTRA_QUERIES */, + getEmptyCombinesError() + ) + ) + ) }; return { packsInputCombines, @@ -86408,12 +86423,15 @@ async function calculateAugmentation(rawPacksInput, rawQueriesInput, repositoryP repoPropertyQueries }; } -function parseQueriesFromInput(rawQueriesInput, queriesInputCombines) { +function parseQueriesFromInput(rawQueriesInput, queriesInputCombines, errorToThrow) { if (!rawQueriesInput) { return void 0; } const trimmedInput = queriesInputCombines ? rawQueriesInput.trim().slice(1).trim() : rawQueriesInput?.trim() ?? ""; if (queriesInputCombines && trimmedInput.length === 0) { + if (errorToThrow) { + throw errorToThrow; + } throw new ConfigurationError( getConfigFilePropertyError( void 0, diff --git a/src/config/db-config.ts b/src/config/db-config.ts index 00f6eaa0c4..501e47a5f6 100644 --- a/src/config/db-config.ts +++ b/src/config/db-config.ts @@ -313,7 +313,16 @@ export async function calculateAugmentation( const repoExtraQueriesCombines = shouldCombine(repoExtraQueries); const repoPropertyQueries = { combines: repoExtraQueriesCombines, - input: parseQueriesFromInput(repoExtraQueries, repoExtraQueriesCombines), + input: parseQueriesFromInput( + repoExtraQueries, + repoExtraQueriesCombines, + new ConfigurationError( + errorMessages.getRepoPropertyError( + RepositoryPropertyName.EXTRA_QUERIES, + errorMessages.getEmptyCombinesError(), + ), + ), + ), }; return { @@ -328,6 +337,7 @@ export async function calculateAugmentation( function parseQueriesFromInput( rawQueriesInput: string | undefined, queriesInputCombines: boolean, + errorToThrow?: ConfigurationError, ) { if (!rawQueriesInput) { return undefined; @@ -337,6 +347,9 @@ function parseQueriesFromInput( ? rawQueriesInput.trim().slice(1).trim() : (rawQueriesInput?.trim() ?? ""); if (queriesInputCombines && trimmedInput.length === 0) { + if (errorToThrow) { + throw errorToThrow; + } throw new ConfigurationError( errorMessages.getConfigFilePropertyError( undefined, diff --git a/src/error-messages.ts b/src/error-messages.ts index 61dd3ef92e..eb49266771 100644 --- a/src/error-messages.ts +++ b/src/error-messages.ts @@ -1,3 +1,5 @@ +import { RepositoryPropertyName } from "./feature-flags/properties"; + const PACKS_PROPERTY = "packs"; export function getConfigFileOutsideWorkspaceErrorMessage( @@ -29,6 +31,10 @@ export function getConfigFileDirectoryGivenMessage(configFile: string): string { return `The configuration file "${configFile}" looks like a directory, not a file`; } +export function getEmptyCombinesError(): string { + return `A '+' was used to specify that you want to add extra arguments to the configuration, but no extra arguments were specified. Please either remove the '+' or specify some extra arguments.`; +} + export function getConfigFilePropertyError( configFile: string | undefined, property: string, @@ -41,6 +47,13 @@ export function getConfigFilePropertyError( } } +export function getRepoPropertyError( + propertyName: RepositoryPropertyName, + error: string, +): string { + return `The repository property "${propertyName}" is invalid: ${error}`; +} + export function getPacksStrInvalid( packStr: string, configFile?: string, From 1bfb67dae0027d2e71865b96aa975a20e6d63349 Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Sat, 20 Sep 2025 13:17:03 +0100 Subject: [PATCH 07/23] Refactor combining queries into its own function --- lib/init-action.js | 15 +++++++++++---- src/config/db-config.ts | 37 ++++++++++++++++++++++++++++--------- 2 files changed, 39 insertions(+), 13 deletions(-) diff --git a/lib/init-action.js b/lib/init-action.js index bef956e4a3..c529cabbd5 100644 --- a/lib/init-action.js +++ b/lib/init-action.js @@ -86442,17 +86442,24 @@ function parseQueriesFromInput(rawQueriesInput, queriesInputCombines, errorToThr } return trimmedInput.split(",").map((query) => ({ uses: query.trim() })); } -function generateCodeScanningConfig(originalUserInput, augmentationProperties) { - const augmentedConfig = cloneObject(originalUserInput); +function combineQueries(augmentedConfig, augmentationProperties) { if (augmentationProperties.queriesInput) { if (augmentationProperties.queriesInputCombines) { - augmentedConfig.queries = (augmentedConfig.queries || []).concat( + return (augmentedConfig.queries || []).concat( augmentationProperties.queriesInput ); } else { - augmentedConfig.queries = augmentationProperties.queriesInput; + return augmentationProperties.queriesInput; } } + return augmentedConfig.queries; +} +function generateCodeScanningConfig(originalUserInput, augmentationProperties) { + const augmentedConfig = cloneObject(originalUserInput); + augmentedConfig.queries = combineQueries( + augmentedConfig, + augmentationProperties + ); if (augmentedConfig.queries?.length === 0) { delete augmentedConfig.queries; } diff --git a/src/config/db-config.ts b/src/config/db-config.ts index 501e47a5f6..231530d11a 100644 --- a/src/config/db-config.ts +++ b/src/config/db-config.ts @@ -361,23 +361,42 @@ function parseQueriesFromInput( return trimmedInput.split(",").map((query) => ({ uses: query.trim() })); } -export function generateCodeScanningConfig( - originalUserInput: UserConfig, +/** + * Combines queries from various configuration sources. + * + * @param augmentedConfig The loaded configuration file (either `config-file` or `config` input). + * @param augmentationProperties Additional configuration data from other sources. + * @returns Returns `augmentedConfig` with `queries` set to the computed array of queries. + */ +function combineQueries( + augmentedConfig: UserConfig, augmentationProperties: AugmentationProperties, -): UserConfig { - // make a copy so we can modify it - const augmentedConfig = cloneObject(originalUserInput); - - // Inject the queries from the input +): QuerySpec[] | undefined { if (augmentationProperties.queriesInput) { if (augmentationProperties.queriesInputCombines) { - augmentedConfig.queries = (augmentedConfig.queries || []).concat( + return (augmentedConfig.queries || []).concat( augmentationProperties.queriesInput, ); } else { - augmentedConfig.queries = augmentationProperties.queriesInput; + return augmentationProperties.queriesInput; } } + + return augmentedConfig.queries; +} + +export function generateCodeScanningConfig( + originalUserInput: UserConfig, + augmentationProperties: AugmentationProperties, +): UserConfig { + // make a copy so we can modify it + const augmentedConfig = cloneObject(originalUserInput); + + // Inject the queries from the input + augmentedConfig.queries = combineQueries( + augmentedConfig, + augmentationProperties, + ); if (augmentedConfig.queries?.length === 0) { delete augmentedConfig.queries; } From d14a2122fdfacff3e6bceaefbf4d7de38b14496a Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Sat, 20 Sep 2025 13:36:01 +0100 Subject: [PATCH 08/23] Include repo property queries in `combineQueries` --- lib/init-action.js | 23 ++++++++++++++------- src/codeql.test.ts | 6 +++--- src/config/db-config.ts | 46 +++++++++++++++++++++++++++++++++-------- 3 files changed, 56 insertions(+), 19 deletions(-) diff --git a/lib/init-action.js b/lib/init-action.js index c529cabbd5..053dfe3c26 100644 --- a/lib/init-action.js +++ b/lib/init-action.js @@ -86442,17 +86442,26 @@ function parseQueriesFromInput(rawQueriesInput, queriesInputCombines, errorToThr } return trimmedInput.split(",").map((query) => ({ uses: query.trim() })); } -function combineQueries(augmentedConfig, augmentationProperties) { +function combineQueries(config, augmentationProperties) { + const result = []; + if (augmentationProperties.repoPropertyQueries && augmentationProperties.repoPropertyQueries.input) { + if (!augmentationProperties.repoPropertyQueries.combines) { + return augmentationProperties.repoPropertyQueries.input; + } else { + result.push(...augmentationProperties.repoPropertyQueries.input); + } + } if (augmentationProperties.queriesInput) { - if (augmentationProperties.queriesInputCombines) { - return (augmentedConfig.queries || []).concat( - augmentationProperties.queriesInput - ); + if (!augmentationProperties.queriesInputCombines) { + return result.concat(augmentationProperties.queriesInput); } else { - return augmentationProperties.queriesInput; + result.push(...augmentationProperties.queriesInput); } } - return augmentedConfig.queries; + if (config.queries) { + result.push(...config.queries); + } + return result; } function generateCodeScanningConfig(originalUserInput, augmentationProperties) { const augmentedConfig = cloneObject(originalUserInput); diff --git a/src/codeql.test.ts b/src/codeql.test.ts index 36775f6530..91cb2a453f 100644 --- a/src/codeql.test.ts +++ b/src/codeql.test.ts @@ -659,15 +659,15 @@ test( }, { queries: [ - { - uses: "zzz", - }, { uses: "xxx", }, { uses: "yyy", }, + { + uses: "zzz", + }, ], }, ); diff --git a/src/config/db-config.ts b/src/config/db-config.ts index 231530d11a..c16b1f0348 100644 --- a/src/config/db-config.ts +++ b/src/config/db-config.ts @@ -364,25 +364,53 @@ function parseQueriesFromInput( /** * Combines queries from various configuration sources. * - * @param augmentedConfig The loaded configuration file (either `config-file` or `config` input). + * @param config The loaded configuration file (either `config-file` or `config` input). * @param augmentationProperties Additional configuration data from other sources. * @returns Returns `augmentedConfig` with `queries` set to the computed array of queries. */ function combineQueries( - augmentedConfig: UserConfig, + config: UserConfig, augmentationProperties: AugmentationProperties, -): QuerySpec[] | undefined { +): QuerySpec[] { + const result: QuerySpec[] = []; + + // Query settings obtained from the repository properties have the highest precedence. + if ( + augmentationProperties.repoPropertyQueries && + augmentationProperties.repoPropertyQueries.input + ) { + // If there are queries configured as a repository property, these may be organisational + // settings. If they don't allow combining with other query configurations, return just the + // ones configured in the repository properties. + if (!augmentationProperties.repoPropertyQueries.combines) { + return augmentationProperties.repoPropertyQueries.input; + } else { + // Otherwise, add them to the query array and continue. + result.push(...augmentationProperties.repoPropertyQueries.input); + } + } + + // If there is a `queries` input to the Action, it has the next highest precedence. if (augmentationProperties.queriesInput) { - if (augmentationProperties.queriesInputCombines) { - return (augmentedConfig.queries || []).concat( - augmentationProperties.queriesInput, - ); + // If there is a `queries` input and `queriesInputCombines` is `false`, then we don't + // combine it with the queries configured in the configuration file (if any). That is the + // original behaviour of this property. However, we DO combine it with any queries that + // we obtained from the repository properties, since that may be enforced by the organisation. + if (!augmentationProperties.queriesInputCombines) { + return result.concat(augmentationProperties.queriesInput); } else { - return augmentationProperties.queriesInput; + // If they combine, add them to the query array and continue. + result.push(...augmentationProperties.queriesInput); } } - return augmentedConfig.queries; + // If we get to this point, we either don't have any extra configuration inputs or all of them + // allow themselves to be combined with the settings from the configuration file. + if (config.queries) { + result.push(...config.queries); + } + + return result; } export function generateCodeScanningConfig( From c7eb488f8f5715587be486eb3391796a0aeea8dd Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Sat, 20 Sep 2025 14:06:20 +0100 Subject: [PATCH 09/23] Add tests --- src/codeql.test.ts | 78 ++++++++++++++++++++++++++++++++++++ src/config/db-config.test.ts | 48 ++++++++++++++++++++++ 2 files changed, 126 insertions(+) diff --git a/src/codeql.test.ts b/src/codeql.test.ts index 91cb2a453f..b80b297255 100644 --- a/src/codeql.test.ts +++ b/src/codeql.test.ts @@ -713,6 +713,84 @@ test( {}, ); +test( + "repo property queries have the highest precedence", + injectedConfigMacro, + { + ...defaultAugmentationProperties, + queriesInputCombines: true, + queriesInput: [{ uses: "xxx" }, { uses: "yyy" }], + repoPropertyQueries: { + combines: false, + input: [{ uses: "zzz" }, { uses: "aaa" }], + }, + }, + { + originalUserInput: { + queries: [{ uses: "uu" }, { uses: "vv" }], + }, + }, + { + queries: [{ uses: "zzz" }, { uses: "aaa" }], + }, +); + +test( + "repo property queries combines with queries input", + injectedConfigMacro, + { + ...defaultAugmentationProperties, + queriesInputCombines: false, + queriesInput: [{ uses: "xxx" }, { uses: "yyy" }], + repoPropertyQueries: { + combines: true, + input: [{ uses: "zzz" }, { uses: "aaa" }], + }, + }, + { + originalUserInput: { + queries: [{ uses: "uu" }, { uses: "vv" }], + }, + }, + { + queries: [ + { uses: "zzz" }, + { uses: "aaa" }, + { uses: "xxx" }, + { uses: "yyy" }, + ], + }, +); + +test( + "repo property queries combines everything else", + injectedConfigMacro, + { + ...defaultAugmentationProperties, + queriesInputCombines: true, + queriesInput: [{ uses: "xxx" }, { uses: "yyy" }], + repoPropertyQueries: { + combines: true, + input: [{ uses: "zzz" }, { uses: "aaa" }], + }, + }, + { + originalUserInput: { + queries: [{ uses: "uu" }, { uses: "vv" }], + }, + }, + { + queries: [ + { uses: "zzz" }, + { uses: "aaa" }, + { uses: "xxx" }, + { uses: "yyy" }, + { uses: "uu" }, + { uses: "vv" }, + ], + }, +); + test("passes a code scanning config AND qlconfig to the CLI", async (t: ExecutionContext) => { await util.withTmpDir(async (tempDir) => { const runnerConstructorStub = stubToolRunnerConstructor(); diff --git a/src/config/db-config.test.ts b/src/config/db-config.test.ts index a5778088f3..b22503475d 100644 --- a/src/config/db-config.test.ts +++ b/src/config/db-config.test.ts @@ -271,6 +271,42 @@ test( }, ); +test( + calculateAugmentationMacro, + "With repo property queries", + undefined, + undefined, + [KnownLanguage.javascript], + { + "github-codeql-extra-queries": "a, b, c, d", + }, + { + ...dbConfig.defaultAugmentationProperties, + repoPropertyQueries: { + combines: false, + input: [{ uses: "a" }, { uses: "b" }, { uses: "c" }, { uses: "d" }], + }, + }, +); + +test( + calculateAugmentationMacro, + "With repo property queries combining", + undefined, + undefined, + [KnownLanguage.javascript], + { + "github-codeql-extra-queries": "+ a, b, c, d", + }, + { + ...dbConfig.defaultAugmentationProperties, + repoPropertyQueries: { + combines: true, + input: [{ uses: "a" }, { uses: "b" }, { uses: "c" }, { uses: "d" }], + }, + }, +); + const calculateAugmentationErrorMacro = test.macro({ exec: async ( t: ExecutionContext, @@ -315,6 +351,18 @@ test( /The workflow property "packs" is invalid/, ); +test( + calculateAugmentationErrorMacro, + "Plus (+) with nothing else (repo property queries)", + undefined, + undefined, + [KnownLanguage.javascript], + { + "github-codeql-extra-queries": " + ", + }, + /The repository property "github-codeql-extra-queries" is invalid/, +); + test( calculateAugmentationErrorMacro, "Packs input with multiple languages", From d46a178adb5c18ab2326b326a5a928c19f7457bb Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Mon, 22 Sep 2025 13:00:15 +0100 Subject: [PATCH 10/23] Sort `queries` array in `check-codescanning-config` --- .github/actions/check-codescanning-config/index.ts | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/.github/actions/check-codescanning-config/index.ts b/.github/actions/check-codescanning-config/index.ts index 0596e4fe9b..a65f0fbd14 100644 --- a/.github/actions/check-codescanning-config/index.ts +++ b/.github/actions/check-codescanning-config/index.ts @@ -6,6 +6,16 @@ import * as assert from 'assert' const actualConfig = loadActualConfig() +function sortConfigArrays(config) { + for (const key in Object.keys(config)) { + const value = config[key]; + if (key === 'queries' && Array.isArray(value)) { + config[key] = value.sort(); + } + } + return config; +} + const rawExpectedConfig = process.argv[3].trim() if (!rawExpectedConfig) { core.setFailed('No expected configuration provided') @@ -18,8 +28,8 @@ if (!rawExpectedConfig) { const expectedConfig = rawExpectedConfig ? JSON.parse(rawExpectedConfig) : undefined; assert.deepStrictEqual( - actualConfig, - expectedConfig, + sortConfigArrays(actualConfig), + sortConfigArrays(expectedConfig), 'Expected configuration does not match actual configuration' ); From 6bb4ad3009f2dc9dde3e060a68c7a20b12105e04 Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Mon, 22 Sep 2025 13:11:32 +0100 Subject: [PATCH 11/23] Update .github/actions/check-codescanning-config/index.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .github/actions/check-codescanning-config/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/actions/check-codescanning-config/index.ts b/.github/actions/check-codescanning-config/index.ts index a65f0fbd14..ea99ca3653 100644 --- a/.github/actions/check-codescanning-config/index.ts +++ b/.github/actions/check-codescanning-config/index.ts @@ -7,7 +7,7 @@ import * as assert from 'assert' const actualConfig = loadActualConfig() function sortConfigArrays(config) { - for (const key in Object.keys(config)) { + for (const key of Object.keys(config)) { const value = config[key]; if (key === 'queries' && Array.isArray(value)) { config[key] = value.sort(); From 54746c8dad543c426853fcde2f214f909fec4b73 Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Mon, 22 Sep 2025 14:56:51 +0100 Subject: [PATCH 12/23] Fix `expected-config-file-contents` --- .github/workflows/codescanning-config-cli.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/codescanning-config-cli.yml b/.github/workflows/codescanning-config-cli.yml index 131c914dd7..316cb7d13c 100644 --- a/.github/workflows/codescanning-config-cli.yml +++ b/.github/workflows/codescanning-config-cli.yml @@ -180,13 +180,13 @@ jobs: with: expected-config-file-contents: | { - "queries": [ - { "uses": "./codeql-qlpacks/complex-javascript-qlpack/foo2/show_ifs.ql" }, - { "uses": "./codeql-qlpacks/complex-javascript-qlpack/show_ifs.ql" } - ], "packs": { "javascript": ["codeql-testing/codeql-pack1@1.0.0", "codeql-testing/codeql-pack2", "codeql/javascript-queries" ] - } + }, + "queries": [ + { "uses": "./codeql-qlpacks/complex-javascript-qlpack/show_ifs.ql" }, + { "uses": "./codeql-qlpacks/complex-javascript-qlpack/foo2/show_ifs.ql" } + ] } languages: javascript queries: + ./codeql-qlpacks/complex-javascript-qlpack/show_ifs.ql From 889d482c541f722cde215c10a6ce573c94cc17d5 Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Mon, 22 Sep 2025 15:17:34 +0100 Subject: [PATCH 13/23] Add logging to `combineQueries` --- lib/init-action.js | 14 ++++++++++++-- src/codeql.test.ts | 3 +++ src/config-utils.ts | 1 + src/config/db-config.ts | 15 +++++++++++++++ 4 files changed, 31 insertions(+), 2 deletions(-) diff --git a/lib/init-action.js b/lib/init-action.js index 053dfe3c26..a5fcd76873 100644 --- a/lib/init-action.js +++ b/lib/init-action.js @@ -86442,10 +86442,15 @@ function parseQueriesFromInput(rawQueriesInput, queriesInputCombines, errorToThr } return trimmedInput.split(",").map((query) => ({ uses: query.trim() })); } -function combineQueries(config, augmentationProperties) { +function combineQueries(logger, config, augmentationProperties) { const result = []; if (augmentationProperties.repoPropertyQueries && augmentationProperties.repoPropertyQueries.input) { if (!augmentationProperties.repoPropertyQueries.combines) { + if (!isDefaultSetup()) { + logger.info( + `Queries are configured in the repository properties and don't allow combining with other query settings. Any queries configured elsewhere will be ignored.` + ); + } return augmentationProperties.repoPropertyQueries.input; } else { result.push(...augmentationProperties.repoPropertyQueries.input); @@ -86463,12 +86468,16 @@ function combineQueries(config, augmentationProperties) { } return result; } -function generateCodeScanningConfig(originalUserInput, augmentationProperties) { +function generateCodeScanningConfig(logger, originalUserInput, augmentationProperties) { const augmentedConfig = cloneObject(originalUserInput); augmentedConfig.queries = combineQueries( + logger, augmentedConfig, augmentationProperties ); + logger.debug( + `Combined queries: ${augmentedConfig.queries?.map((q) => q.uses).join(",")}` + ); if (augmentedConfig.queries?.length === 0) { delete augmentedConfig.queries; } @@ -87614,6 +87623,7 @@ async function initActionState({ logger ); const computedConfig = generateCodeScanningConfig( + logger, userConfig, augmentationProperties ); diff --git a/src/codeql.test.ts b/src/codeql.test.ts index b80b297255..a5422b1e38 100644 --- a/src/codeql.test.ts +++ b/src/codeql.test.ts @@ -496,6 +496,8 @@ const injectedConfigMacro = test.macro({ expectedConfig: any, ) => { await util.withTmpDir(async (tempDir) => { + sinon.stub(actionsUtil, "isDefaultSetup").resolves(false); + const runnerConstructorStub = stubToolRunnerConstructor(); const codeqlObject = await stubCodeql(); @@ -505,6 +507,7 @@ const injectedConfigMacro = test.macro({ tempDir, }; thisStubConfig.computedConfig = generateCodeScanningConfig( + getRunnerLogger(true), thisStubConfig.originalUserInput, augmentationProperties, ); diff --git a/src/config-utils.ts b/src/config-utils.ts index d20cf3bae5..e6e99be3b8 100644 --- a/src/config-utils.ts +++ b/src/config-utils.ts @@ -473,6 +473,7 @@ export async function initActionState( // Compute the full Code Scanning configuration that combines the configuration from the // configuration file / `config` input with other inputs, such as `queries`. const computedConfig = generateCodeScanningConfig( + logger, userConfig, augmentationProperties, ); diff --git a/src/config/db-config.ts b/src/config/db-config.ts index c16b1f0348..8e6dc501d7 100644 --- a/src/config/db-config.ts +++ b/src/config/db-config.ts @@ -2,12 +2,14 @@ import * as path from "path"; import * as semver from "semver"; +import { isDefaultSetup } from "../actions-util"; import * as errorMessages from "../error-messages"; import { RepositoryProperties, RepositoryPropertyName, } from "../feature-flags/properties"; import { Language } from "../languages"; +import { Logger } from "../logging"; import { cloneObject, ConfigurationError, prettyPrintPack } from "../util"; export interface ExcludeQueryFilter { @@ -364,11 +366,13 @@ function parseQueriesFromInput( /** * Combines queries from various configuration sources. * + * @param logger The logger to use. * @param config The loaded configuration file (either `config-file` or `config` input). * @param augmentationProperties Additional configuration data from other sources. * @returns Returns `augmentedConfig` with `queries` set to the computed array of queries. */ function combineQueries( + logger: Logger, config: UserConfig, augmentationProperties: AugmentationProperties, ): QuerySpec[] { @@ -383,6 +387,12 @@ function combineQueries( // settings. If they don't allow combining with other query configurations, return just the // ones configured in the repository properties. if (!augmentationProperties.repoPropertyQueries.combines) { + if (!isDefaultSetup()) { + logger.info( + `Queries are configured in the repository properties and don't allow combining with other query settings. ` + + `Any queries configured elsewhere will be ignored.`, + ); + } return augmentationProperties.repoPropertyQueries.input; } else { // Otherwise, add them to the query array and continue. @@ -414,6 +424,7 @@ function combineQueries( } export function generateCodeScanningConfig( + logger: Logger, originalUserInput: UserConfig, augmentationProperties: AugmentationProperties, ): UserConfig { @@ -422,9 +433,13 @@ export function generateCodeScanningConfig( // Inject the queries from the input augmentedConfig.queries = combineQueries( + logger, augmentedConfig, augmentationProperties, ); + logger.debug( + `Combined queries: ${augmentedConfig.queries?.map((q) => q.uses).join(",")}`, + ); if (augmentedConfig.queries?.length === 0) { delete augmentedConfig.queries; } From 05310c6f55c5e8bf6c3da2745cb66aaaecac96c1 Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Mon, 22 Sep 2025 16:23:54 +0100 Subject: [PATCH 14/23] Ignore repository property query config if CQ-only analysis --- lib/init-action.js | 9 +++++++ src/config-utils.test.ts | 58 ++++++++++++++++++++++++++++++++++++++++ src/config-utils.ts | 18 +++++++++++++ 3 files changed, 85 insertions(+) diff --git a/lib/init-action.js b/lib/init-action.js index a5fcd76873..bf8138277b 100644 --- a/lib/init-action.js +++ b/lib/init-action.js @@ -87616,6 +87616,15 @@ async function initActionState({ repositoryProperties, languages ); + if (analysisKinds.length === 1 && analysisKinds.includes("code-quality" /* CodeQuality */) && augmentationProperties.repoPropertyQueries.input) { + logger.info( + `Ignoring queries configured in the repository properties, because query customisations are not supported for Code Quality analyses.` + ); + augmentationProperties.repoPropertyQueries = { + combines: false, + input: void 0 + }; + } const { trapCaches, trapCacheDownloadTime } = await downloadCacheWithTime( trapCachingEnabled, codeql, diff --git a/src/config-utils.test.ts b/src/config-utils.test.ts index 1df142f118..566a719ca0 100644 --- a/src/config-utils.test.ts +++ b/src/config-utils.test.ts @@ -29,6 +29,7 @@ import { getRecordingLogger, LoggedMessage, mockCodeQLVersion, + createTestConfig, } from "./testing-utils"; import { GitHubVariant, @@ -230,6 +231,63 @@ test("load code quality config", async (t) => { }); }); +test("initActionState doesn't throw if there are queries configured in the repository properties", async (t) => { + return await withTmpDir(async (tempDir) => { + const logger = getRunnerLogger(true); + const languages = "javascript"; + + const codeql = createStubCodeQL({ + async betterResolveLanguages() { + return { + extractors: { + javascript: [{ extractor_root: "" }], + }, + }; + }, + }); + + // This should be ignored and no error should be thrown. + const repositoryProperties = { + "github-codeql-extra-queries": "+foo", + }; + + // Expected configuration for a CQ-only analysis. + const computedConfig: configUtils.UserConfig = { + "disable-default-queries": true, + queries: [{ uses: "code-quality" }], + "query-filters": [], + }; + + const expectedConfig = createTestConfig({ + analysisKinds: [AnalysisKind.CodeQuality], + languages: [KnownLanguage.javascript], + codeQLCmd: codeql.getPath(), + computedConfig, + dbLocation: path.resolve(tempDir, "codeql_databases"), + debugArtifactName: "", + debugDatabaseName: "", + tempDir, + repositoryProperties, + }); + + await t.notThrowsAsync(async () => { + const config = await configUtils.initConfig( + createTestInitConfigInputs({ + analysisKindsInput: "code-quality", + languagesInput: languages, + repository: { owner: "github", repo: "example" }, + tempDir, + codeql, + repositoryProperties, + logger, + }), + ); + + t.deepEqual(config, expectedConfig); + }); + }); +}); + test("loading a saved config produces the same config", async (t) => { return await withTmpDir(async (tempDir) => { const logger = getRunnerLogger(true); diff --git a/src/config-utils.ts b/src/config-utils.ts index e6e99be3b8..fe4b392ab2 100644 --- a/src/config-utils.ts +++ b/src/config-utils.ts @@ -463,6 +463,24 @@ export async function initActionState( languages, ); + // If `code-quality` is the only enabled analysis kind, we don't support query customisation. + // It would be a problem if queries that are configured in repository properties cause `code-quality`-only + // analyses to break. We therefore ignore query customisations that are configured in repository properties + // if `code-quality` is the only enabled analysis kind. + if ( + analysisKinds.length === 1 && + analysisKinds.includes(AnalysisKind.CodeQuality) && + augmentationProperties.repoPropertyQueries.input + ) { + logger.info( + `Ignoring queries configured in the repository properties, because query customisations are not supported for Code Quality analyses.`, + ); + augmentationProperties.repoPropertyQueries = { + combines: false, + input: undefined, + }; + } + const { trapCaches, trapCacheDownloadTime } = await downloadCacheWithTime( trapCachingEnabled, codeql, From b4f966a31a29b19fe0a91329d3192df5b24aafd1 Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Mon, 22 Sep 2025 20:26:56 +0100 Subject: [PATCH 15/23] Add FF to control whether to fetch repository properties --- lib/analyze-action-post.js | 5 +++++ lib/analyze-action.js | 5 +++++ lib/autobuild-action.js | 5 +++++ lib/init-action-post.js | 5 +++++ lib/init-action.js | 11 ++++++++--- lib/resolve-environment-action.js | 5 +++++ lib/start-proxy-action-post.js | 5 +++++ lib/upload-lib.js | 5 +++++ lib/upload-sarif-action-post.js | 5 +++++ lib/upload-sarif-action.js | 5 +++++ src/feature-flags.ts | 6 ++++++ src/init-action.ts | 8 +++++--- 12 files changed, 64 insertions(+), 6 deletions(-) diff --git a/lib/analyze-action-post.js b/lib/analyze-action-post.js index f2a5fc7b76..db641a89a9 100644 --- a/lib/analyze-action-post.js +++ b/lib/analyze-action-post.js @@ -117922,6 +117922,11 @@ var featureConfig = { minimumVersion: void 0, toolsFeature: "pythonDefaultIsToNotExtractStdlib" /* PythonDefaultIsToNotExtractStdlib */ }, + ["use_repository_properties" /* UseRepositoryProperties */]: { + defaultValue: false, + envVar: "CODEQL_ACTION_USE_REPOSITORY_PROPERTIES", + minimumVersion: void 0 + }, ["qa_telemetry_enabled" /* QaTelemetryEnabled */]: { defaultValue: false, envVar: "CODEQL_ACTION_QA_TELEMETRY", diff --git a/lib/analyze-action.js b/lib/analyze-action.js index 21150349ba..ec0f083cf8 100644 --- a/lib/analyze-action.js +++ b/lib/analyze-action.js @@ -91156,6 +91156,11 @@ var featureConfig = { minimumVersion: void 0, toolsFeature: "pythonDefaultIsToNotExtractStdlib" /* PythonDefaultIsToNotExtractStdlib */ }, + ["use_repository_properties" /* UseRepositoryProperties */]: { + defaultValue: false, + envVar: "CODEQL_ACTION_USE_REPOSITORY_PROPERTIES", + minimumVersion: void 0 + }, ["qa_telemetry_enabled" /* QaTelemetryEnabled */]: { defaultValue: false, envVar: "CODEQL_ACTION_QA_TELEMETRY", diff --git a/lib/autobuild-action.js b/lib/autobuild-action.js index d442a65d07..1b022d3788 100644 --- a/lib/autobuild-action.js +++ b/lib/autobuild-action.js @@ -78660,6 +78660,11 @@ var featureConfig = { minimumVersion: void 0, toolsFeature: "pythonDefaultIsToNotExtractStdlib" /* PythonDefaultIsToNotExtractStdlib */ }, + ["use_repository_properties" /* UseRepositoryProperties */]: { + defaultValue: false, + envVar: "CODEQL_ACTION_USE_REPOSITORY_PROPERTIES", + minimumVersion: void 0 + }, ["qa_telemetry_enabled" /* QaTelemetryEnabled */]: { defaultValue: false, envVar: "CODEQL_ACTION_QA_TELEMETRY", diff --git a/lib/init-action-post.js b/lib/init-action-post.js index b1ae225861..83a0f07f2b 100644 --- a/lib/init-action-post.js +++ b/lib/init-action-post.js @@ -129255,6 +129255,11 @@ var featureConfig = { minimumVersion: void 0, toolsFeature: "pythonDefaultIsToNotExtractStdlib" /* PythonDefaultIsToNotExtractStdlib */ }, + ["use_repository_properties" /* UseRepositoryProperties */]: { + defaultValue: false, + envVar: "CODEQL_ACTION_USE_REPOSITORY_PROPERTIES", + minimumVersion: void 0 + }, ["qa_telemetry_enabled" /* QaTelemetryEnabled */]: { defaultValue: false, envVar: "CODEQL_ACTION_QA_TELEMETRY", diff --git a/lib/init-action.js b/lib/init-action.js index bf8138277b..fc69e0ec21 100644 --- a/lib/init-action.js +++ b/lib/init-action.js @@ -87049,6 +87049,11 @@ var featureConfig = { minimumVersion: void 0, toolsFeature: "pythonDefaultIsToNotExtractStdlib" /* PythonDefaultIsToNotExtractStdlib */ }, + ["use_repository_properties" /* UseRepositoryProperties */]: { + defaultValue: false, + envVar: "CODEQL_ACTION_USE_REPOSITORY_PROPERTIES", + minimumVersion: void 0 + }, ["qa_telemetry_enabled" /* QaTelemetryEnabled */]: { defaultValue: false, envVar: "CODEQL_ACTION_QA_TELEMETRY", @@ -90493,10 +90498,10 @@ async function run() { getTemporaryDirectory(), logger ); - const repositoryProperties = await loadPropertiesFromApi( - logger, - repositoryNwo + const enableRepoProps = await features.getValue( + "use_repository_properties" /* UseRepositoryProperties */ ); + const repositoryProperties = enableRepoProps ? await loadPropertiesFromApi(logger, repositoryNwo) : {}; const jobRunUuid = v4_default(); logger.info(`Job run UUID is ${jobRunUuid}.`); core13.exportVariable("JOB_RUN_UUID" /* JOB_RUN_UUID */, jobRunUuid); diff --git a/lib/resolve-environment-action.js b/lib/resolve-environment-action.js index 447d12c654..7306a6f0d6 100644 --- a/lib/resolve-environment-action.js +++ b/lib/resolve-environment-action.js @@ -78651,6 +78651,11 @@ var featureConfig = { minimumVersion: void 0, toolsFeature: "pythonDefaultIsToNotExtractStdlib" /* PythonDefaultIsToNotExtractStdlib */ }, + ["use_repository_properties" /* UseRepositoryProperties */]: { + defaultValue: false, + envVar: "CODEQL_ACTION_USE_REPOSITORY_PROPERTIES", + minimumVersion: void 0 + }, ["qa_telemetry_enabled" /* QaTelemetryEnabled */]: { defaultValue: false, envVar: "CODEQL_ACTION_QA_TELEMETRY", diff --git a/lib/start-proxy-action-post.js b/lib/start-proxy-action-post.js index adcfb47e4d..5234d9f125 100644 --- a/lib/start-proxy-action-post.js +++ b/lib/start-proxy-action-post.js @@ -117331,6 +117331,11 @@ var featureConfig = { minimumVersion: void 0, toolsFeature: "pythonDefaultIsToNotExtractStdlib" /* PythonDefaultIsToNotExtractStdlib */ }, + ["use_repository_properties" /* UseRepositoryProperties */]: { + defaultValue: false, + envVar: "CODEQL_ACTION_USE_REPOSITORY_PROPERTIES", + minimumVersion: void 0 + }, ["qa_telemetry_enabled" /* QaTelemetryEnabled */]: { defaultValue: false, envVar: "CODEQL_ACTION_QA_TELEMETRY", diff --git a/lib/upload-lib.js b/lib/upload-lib.js index fe2660b72e..49cec9332b 100644 --- a/lib/upload-lib.js +++ b/lib/upload-lib.js @@ -89347,6 +89347,11 @@ var featureConfig = { minimumVersion: void 0, toolsFeature: "pythonDefaultIsToNotExtractStdlib" /* PythonDefaultIsToNotExtractStdlib */ }, + ["use_repository_properties" /* UseRepositoryProperties */]: { + defaultValue: false, + envVar: "CODEQL_ACTION_USE_REPOSITORY_PROPERTIES", + minimumVersion: void 0 + }, ["qa_telemetry_enabled" /* QaTelemetryEnabled */]: { defaultValue: false, envVar: "CODEQL_ACTION_QA_TELEMETRY", diff --git a/lib/upload-sarif-action-post.js b/lib/upload-sarif-action-post.js index 65af1249c2..5737173053 100644 --- a/lib/upload-sarif-action-post.js +++ b/lib/upload-sarif-action-post.js @@ -117494,6 +117494,11 @@ var featureConfig = { minimumVersion: void 0, toolsFeature: "pythonDefaultIsToNotExtractStdlib" /* PythonDefaultIsToNotExtractStdlib */ }, + ["use_repository_properties" /* UseRepositoryProperties */]: { + defaultValue: false, + envVar: "CODEQL_ACTION_USE_REPOSITORY_PROPERTIES", + minimumVersion: void 0 + }, ["qa_telemetry_enabled" /* QaTelemetryEnabled */]: { defaultValue: false, envVar: "CODEQL_ACTION_QA_TELEMETRY", diff --git a/lib/upload-sarif-action.js b/lib/upload-sarif-action.js index f4a55884c0..0b63c40c5d 100644 --- a/lib/upload-sarif-action.js +++ b/lib/upload-sarif-action.js @@ -89335,6 +89335,11 @@ var featureConfig = { minimumVersion: void 0, toolsFeature: "pythonDefaultIsToNotExtractStdlib" /* PythonDefaultIsToNotExtractStdlib */ }, + ["use_repository_properties" /* UseRepositoryProperties */]: { + defaultValue: false, + envVar: "CODEQL_ACTION_USE_REPOSITORY_PROPERTIES", + minimumVersion: void 0 + }, ["qa_telemetry_enabled" /* QaTelemetryEnabled */]: { defaultValue: false, envVar: "CODEQL_ACTION_QA_TELEMETRY", diff --git a/src/feature-flags.ts b/src/feature-flags.ts index b7946d62f4..2938f5108c 100644 --- a/src/feature-flags.ts +++ b/src/feature-flags.ts @@ -73,6 +73,7 @@ export enum Feature { OverlayAnalysisRust = "overlay_analysis_rust", OverlayAnalysisSwift = "overlay_analysis_swift", PythonDefaultIsToNotExtractStdlib = "python_default_is_to_not_extract_stdlib", + UseRepositoryProperties = "use_repository_properties", QaTelemetryEnabled = "qa_telemetry_enabled", ResolveSupportedLanguagesUsingCli = "resolve_supported_languages_using_cli", } @@ -264,6 +265,11 @@ export const featureConfig: Record< minimumVersion: undefined, toolsFeature: ToolsFeature.PythonDefaultIsToNotExtractStdlib, }, + [Feature.UseRepositoryProperties]: { + defaultValue: false, + envVar: "CODEQL_ACTION_USE_REPOSITORY_PROPERTIES", + minimumVersion: undefined, + }, [Feature.QaTelemetryEnabled]: { defaultValue: false, envVar: "CODEQL_ACTION_QA_TELEMETRY", diff --git a/src/init-action.ts b/src/init-action.ts index eeb14c78b2..07e03a2c76 100644 --- a/src/init-action.ts +++ b/src/init-action.ts @@ -198,10 +198,12 @@ async function run() { ); // Fetch the values of known repository properties that affect us. - const repositoryProperties = await loadPropertiesFromApi( - logger, - repositoryNwo, + const enableRepoProps = await features.getValue( + Feature.UseRepositoryProperties, ); + const repositoryProperties = enableRepoProps + ? await loadPropertiesFromApi(logger, repositoryNwo) + : {}; const jobRunUuid = uuidV4(); logger.info(`Job run UUID is ${jobRunUuid}.`); From 40262b1861cc25df775c1fbf4ccc60699362b235 Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Tue, 23 Sep 2025 09:10:52 +0100 Subject: [PATCH 16/23] Add `getRepositoryProperties` to `api-client`, for easier mocking --- lib/init-action.js | 14 +++++++------- src/api-client.ts | 10 +++++++++- src/feature-flags/properties.ts | 10 ++-------- 3 files changed, 18 insertions(+), 16 deletions(-) diff --git a/lib/init-action.js b/lib/init-action.js index fc69e0ec21..d4aa4d0a5e 100644 --- a/lib/init-action.js +++ b/lib/init-action.js @@ -86128,6 +86128,12 @@ function computeAutomationID(analysis_key, environment) { } return automationID; } +async function getRepositoryProperties(repositoryNwo) { + return getApiClient().request("GET /repos/:owner/:repo/properties/values", { + owner: repositoryNwo.owner, + repo: repositoryNwo.repo + }); +} // src/caching-utils.ts var core6 = __toESM(require_core()); @@ -86257,13 +86263,7 @@ var RepositoryPropertyName = /* @__PURE__ */ ((RepositoryPropertyName2) => { })(RepositoryPropertyName || {}); async function loadPropertiesFromApi(logger, repositoryNwo) { try { - const response = await getApiClient().request( - "GET /repos/:owner/:repo/properties/values", - { - owner: repositoryNwo.owner, - repo: repositoryNwo.repo - } - ); + const response = await getRepositoryProperties(repositoryNwo); const remoteProperties = response.data; if (!Array.isArray(remoteProperties)) { throw new Error( diff --git a/src/api-client.ts b/src/api-client.ts index 207b3c86af..8e4a30c571 100644 --- a/src/api-client.ts +++ b/src/api-client.ts @@ -4,7 +4,7 @@ import * as retry from "@octokit/plugin-retry"; import consoleLogLevel from "console-log-level"; import { getActionVersion, getRequiredInput } from "./actions-util"; -import { getRepositoryNwo } from "./repository"; +import { getRepositoryNwo, RepositoryNwo } from "./repository"; import { ConfigurationError, getRequiredEnvParam, @@ -240,6 +240,14 @@ export async function deleteActionsCache(id: number) { }); } +/** Retrieve all custom repository properties. */ +export async function getRepositoryProperties(repositoryNwo: RepositoryNwo) { + return getApiClient().request("GET /repos/:owner/:repo/properties/values", { + owner: repositoryNwo.owner, + repo: repositoryNwo.repo, + }); +} + export function wrapApiConfigurationError(e: unknown) { if (isHTTPError(e)) { if ( diff --git a/src/feature-flags/properties.ts b/src/feature-flags/properties.ts index fc0905e557..4e9f25c20a 100644 --- a/src/feature-flags/properties.ts +++ b/src/feature-flags/properties.ts @@ -1,4 +1,4 @@ -import { getApiClient } from "../api-client"; +import { getRepositoryProperties } from "../api-client"; import { Logger } from "../logging"; import { RepositoryNwo } from "../repository"; @@ -41,13 +41,7 @@ export async function loadPropertiesFromApi( repositoryNwo: RepositoryNwo, ): Promise { try { - const response = await getApiClient().request( - "GET /repos/:owner/:repo/properties/values", - { - owner: repositoryNwo.owner, - repo: repositoryNwo.repo, - }, - ); + const response = await getRepositoryProperties(repositoryNwo); const remoteProperties = response.data as GitHubPropertiesResponse; if (!Array.isArray(remoteProperties)) { From 07920e84f8420319b98a4761d927ab959ce22947 Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Tue, 23 Sep 2025 09:12:16 +0100 Subject: [PATCH 17/23] Fix using `keys` instead of `values` Also add `logger.debug` call with keys from API response --- lib/init-action.js | 5 ++++- src/feature-flags/properties.ts | 10 ++++++++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/lib/init-action.js b/lib/init-action.js index d4aa4d0a5e..2354029a89 100644 --- a/lib/init-action.js +++ b/lib/init-action.js @@ -86270,7 +86270,10 @@ async function loadPropertiesFromApi(logger, repositoryNwo) { `Expected repository properties API to return an array, but got: ${JSON.stringify(response.data)}` ); } - const knownProperties = new Set(Object.keys(RepositoryPropertyName)); + logger.debug( + `Retrieved ${remoteProperties.length} repository properties: ${remoteProperties.map((p) => p.property_name).join(", ")}` + ); + const knownProperties = new Set(Object.values(RepositoryPropertyName)); const properties = {}; for (const property of remoteProperties) { if (property.property_name === void 0) { diff --git a/src/feature-flags/properties.ts b/src/feature-flags/properties.ts index 4e9f25c20a..a644007268 100644 --- a/src/feature-flags/properties.ts +++ b/src/feature-flags/properties.ts @@ -50,7 +50,11 @@ export async function loadPropertiesFromApi( ); } - const knownProperties = new Set(Object.keys(RepositoryPropertyName)); + logger.debug( + `Retrieved ${remoteProperties.length} repository properties: ${remoteProperties.map((p) => p.property_name).join(", ")}`, + ); + + const knownProperties = new Set(Object.values(RepositoryPropertyName)); const properties: RepositoryProperties = {}; for (const property of remoteProperties) { if (property.property_name === undefined) { @@ -59,7 +63,9 @@ export async function loadPropertiesFromApi( ); } - if (knownProperties.has(property.property_name)) { + if ( + knownProperties.has(property.property_name as RepositoryPropertyName) + ) { properties[property.property_name] = property.value; } } From 7f73f8c23501e9300577bfa360d969ebe259b1da Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Tue, 23 Sep 2025 09:16:28 +0100 Subject: [PATCH 18/23] Add unit tests for `properties` module --- src/feature-flags/properties.test.ts | 58 ++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 src/feature-flags/properties.test.ts diff --git a/src/feature-flags/properties.test.ts b/src/feature-flags/properties.test.ts new file mode 100644 index 0000000000..66e5ca9794 --- /dev/null +++ b/src/feature-flags/properties.test.ts @@ -0,0 +1,58 @@ +import test from "ava"; +import * as sinon from "sinon"; + +import * as api from "../api-client"; +import { getRunnerLogger } from "../logging"; +import { parseRepositoryNwo } from "../repository"; +import { setupTests } from "../testing-utils"; + +import * as properties from "./properties"; + +setupTests(test); + +test("loadPropertiesFromApi throws if response data is not an array", async (t) => { + sinon.stub(api, "getRepositoryProperties").resolves({ + headers: {}, + status: 200, + url: "", + data: {}, + }); + const logger = getRunnerLogger(true); + const mockRepositoryNwo = parseRepositoryNwo("owner/repo"); + await t.throwsAsync( + properties.loadPropertiesFromApi(logger, mockRepositoryNwo), + ); +}); + +test("loadPropertiesFromApi throws if response data contains unexpected objects", async (t) => { + sinon.stub(api, "getRepositoryProperties").resolves({ + headers: {}, + status: 200, + url: "", + data: [{}], + }); + const logger = getRunnerLogger(true); + const mockRepositoryNwo = parseRepositoryNwo("owner/repo"); + await t.throwsAsync( + properties.loadPropertiesFromApi(logger, mockRepositoryNwo), + ); +}); + +test("loadPropertiesFromApi loads known properties", async (t) => { + sinon.stub(api, "getRepositoryProperties").resolves({ + headers: {}, + status: 200, + url: "", + data: [ + { property_name: "github-codeql-extra-queries", value: "+queries" }, + { property_name: "unknown-property", value: "something" }, + ] satisfies properties.RepositoryProperty[], + }); + const logger = getRunnerLogger(true); + const mockRepositoryNwo = parseRepositoryNwo("owner/repo"); + const response = await properties.loadPropertiesFromApi( + logger, + mockRepositoryNwo, + ); + t.deepEqual(response, { "github-codeql-extra-queries": "+queries" }); +}); From 0a75581cde9a771df3b5a2b2556719a740418d5f Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Tue, 23 Sep 2025 09:22:07 +0100 Subject: [PATCH 19/23] Check that we are on dotcom --- src/feature-flags/properties.test.ts | 43 ++++++++++++++++++++++++++-- src/feature-flags/properties.ts | 8 ++++++ src/init-action.ts | 2 +- 3 files changed, 50 insertions(+), 3 deletions(-) diff --git a/src/feature-flags/properties.test.ts b/src/feature-flags/properties.test.ts index 66e5ca9794..ed351cb528 100644 --- a/src/feature-flags/properties.test.ts +++ b/src/feature-flags/properties.test.ts @@ -5,6 +5,7 @@ import * as api from "../api-client"; import { getRunnerLogger } from "../logging"; import { parseRepositoryNwo } from "../repository"; import { setupTests } from "../testing-utils"; +import * as util from "../util"; import * as properties from "./properties"; @@ -20,7 +21,13 @@ test("loadPropertiesFromApi throws if response data is not an array", async (t) const logger = getRunnerLogger(true); const mockRepositoryNwo = parseRepositoryNwo("owner/repo"); await t.throwsAsync( - properties.loadPropertiesFromApi(logger, mockRepositoryNwo), + properties.loadPropertiesFromApi( + { + type: util.GitHubVariant.DOTCOM, + }, + logger, + mockRepositoryNwo, + ), ); }); @@ -34,10 +41,39 @@ test("loadPropertiesFromApi throws if response data contains unexpected objects" const logger = getRunnerLogger(true); const mockRepositoryNwo = parseRepositoryNwo("owner/repo"); await t.throwsAsync( - properties.loadPropertiesFromApi(logger, mockRepositoryNwo), + properties.loadPropertiesFromApi( + { + type: util.GitHubVariant.DOTCOM, + }, + logger, + mockRepositoryNwo, + ), ); }); +test("loadPropertiesFromApi returns empty object if not on dotcom", async (t) => { + sinon.stub(api, "getRepositoryProperties").resolves({ + headers: {}, + status: 200, + url: "", + data: [ + { property_name: "github-codeql-extra-queries", value: "+queries" }, + { property_name: "unknown-property", value: "something" }, + ] satisfies properties.RepositoryProperty[], + }); + const logger = getRunnerLogger(true); + const mockRepositoryNwo = parseRepositoryNwo("owner/repo"); + const response = await properties.loadPropertiesFromApi( + { + type: util.GitHubVariant.GHES, + version: "", + }, + logger, + mockRepositoryNwo, + ); + t.deepEqual(response, {}); +}); + test("loadPropertiesFromApi loads known properties", async (t) => { sinon.stub(api, "getRepositoryProperties").resolves({ headers: {}, @@ -51,6 +87,9 @@ test("loadPropertiesFromApi loads known properties", async (t) => { const logger = getRunnerLogger(true); const mockRepositoryNwo = parseRepositoryNwo("owner/repo"); const response = await properties.loadPropertiesFromApi( + { + type: util.GitHubVariant.DOTCOM, + }, logger, mockRepositoryNwo, ); diff --git a/src/feature-flags/properties.ts b/src/feature-flags/properties.ts index a644007268..8c9a94a2d4 100644 --- a/src/feature-flags/properties.ts +++ b/src/feature-flags/properties.ts @@ -1,6 +1,7 @@ import { getRepositoryProperties } from "../api-client"; import { Logger } from "../logging"; import { RepositoryNwo } from "../repository"; +import { GitHubVariant, GitHubVersion } from "../util"; /** * Enumerates repository property names that have some meaning to us. @@ -37,9 +38,16 @@ export type RepositoryProperties = Partial< * @returns Returns a partial mapping from `RepositoryPropertyName` to values. */ export async function loadPropertiesFromApi( + gitHubVersion: GitHubVersion, logger: Logger, repositoryNwo: RepositoryNwo, ): Promise { + // TODO: To be safe for now; later we should replace this with a version check once we know + // which version of GHES we expect this to be supported by. + if (gitHubVersion.type !== GitHubVariant.DOTCOM) { + return {}; + } + try { const response = await getRepositoryProperties(repositoryNwo); const remoteProperties = response.data as GitHubPropertiesResponse; diff --git a/src/init-action.ts b/src/init-action.ts index 07e03a2c76..2b4dba3fcf 100644 --- a/src/init-action.ts +++ b/src/init-action.ts @@ -202,7 +202,7 @@ async function run() { Feature.UseRepositoryProperties, ); const repositoryProperties = enableRepoProps - ? await loadPropertiesFromApi(logger, repositoryNwo) + ? await loadPropertiesFromApi(gitHubVersion, logger, repositoryNwo) : {}; const jobRunUuid = uuidV4(); From 205b6ba838a620bb54ffaf4287ee1f6f10747a84 Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Tue, 23 Sep 2025 09:29:04 +0100 Subject: [PATCH 20/23] Rebuild --- lib/init-action.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/init-action.js b/lib/init-action.js index 2354029a89..999135d85b 100644 --- a/lib/init-action.js +++ b/lib/init-action.js @@ -86261,7 +86261,10 @@ var RepositoryPropertyName = /* @__PURE__ */ ((RepositoryPropertyName2) => { RepositoryPropertyName2["EXTRA_QUERIES"] = "github-codeql-extra-queries"; return RepositoryPropertyName2; })(RepositoryPropertyName || {}); -async function loadPropertiesFromApi(logger, repositoryNwo) { +async function loadPropertiesFromApi(gitHubVersion, logger, repositoryNwo) { + if (gitHubVersion.type !== 0 /* DOTCOM */) { + return {}; + } try { const response = await getRepositoryProperties(repositoryNwo); const remoteProperties = response.data; @@ -90504,7 +90507,7 @@ async function run() { const enableRepoProps = await features.getValue( "use_repository_properties" /* UseRepositoryProperties */ ); - const repositoryProperties = enableRepoProps ? await loadPropertiesFromApi(logger, repositoryNwo) : {}; + const repositoryProperties = enableRepoProps ? await loadPropertiesFromApi(gitHubVersion, logger, repositoryNwo) : {}; const jobRunUuid = v4_default(); logger.info(`Job run UUID is ${jobRunUuid}.`); core13.exportVariable("JOB_RUN_UUID" /* JOB_RUN_UUID */, jobRunUuid); From 4178e15b0aa7bab48cfa80d490a57a82bda10822 Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Tue, 23 Sep 2025 09:41:53 +0100 Subject: [PATCH 21/23] Only disable `loadPropertiesFromApi` on GHES --- src/feature-flags/properties.test.ts | 2 +- src/feature-flags/properties.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/feature-flags/properties.test.ts b/src/feature-flags/properties.test.ts index ed351cb528..dd0c72a21e 100644 --- a/src/feature-flags/properties.test.ts +++ b/src/feature-flags/properties.test.ts @@ -51,7 +51,7 @@ test("loadPropertiesFromApi throws if response data contains unexpected objects" ); }); -test("loadPropertiesFromApi returns empty object if not on dotcom", async (t) => { +test("loadPropertiesFromApi returns empty object if on GHES", async (t) => { sinon.stub(api, "getRepositoryProperties").resolves({ headers: {}, status: 200, diff --git a/src/feature-flags/properties.ts b/src/feature-flags/properties.ts index 8c9a94a2d4..0104cddd91 100644 --- a/src/feature-flags/properties.ts +++ b/src/feature-flags/properties.ts @@ -44,7 +44,7 @@ export async function loadPropertiesFromApi( ): Promise { // TODO: To be safe for now; later we should replace this with a version check once we know // which version of GHES we expect this to be supported by. - if (gitHubVersion.type !== GitHubVariant.DOTCOM) { + if (gitHubVersion.type === GitHubVariant.GHES) { return {}; } From 54bbe822cc10f91ef17d25baf270b272709b7579 Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Tue, 23 Sep 2025 09:44:52 +0100 Subject: [PATCH 22/23] Always log when queries are configured in the repository properties --- lib/init-action.js | 5 ++++- src/config/db-config.ts | 5 +++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/lib/init-action.js b/lib/init-action.js index 999135d85b..8ff16f4540 100644 --- a/lib/init-action.js +++ b/lib/init-action.js @@ -86262,7 +86262,7 @@ var RepositoryPropertyName = /* @__PURE__ */ ((RepositoryPropertyName2) => { return RepositoryPropertyName2; })(RepositoryPropertyName || {}); async function loadPropertiesFromApi(gitHubVersion, logger, repositoryNwo) { - if (gitHubVersion.type !== 0 /* DOTCOM */) { + if (gitHubVersion.type === 1 /* GHES */) { return {}; } try { @@ -86451,6 +86451,9 @@ function parseQueriesFromInput(rawQueriesInput, queriesInputCombines, errorToThr function combineQueries(logger, config, augmentationProperties) { const result = []; if (augmentationProperties.repoPropertyQueries && augmentationProperties.repoPropertyQueries.input) { + logger.info( + `Found query configuration in the repository properties (${"github-codeql-extra-queries" /* EXTRA_QUERIES */}): ${augmentationProperties.repoPropertyQueries.input.map((q) => q.uses).join(", ")}` + ); if (!augmentationProperties.repoPropertyQueries.combines) { if (!isDefaultSetup()) { logger.info( diff --git a/src/config/db-config.ts b/src/config/db-config.ts index 8e6dc501d7..58e8cae45c 100644 --- a/src/config/db-config.ts +++ b/src/config/db-config.ts @@ -383,6 +383,11 @@ function combineQueries( augmentationProperties.repoPropertyQueries && augmentationProperties.repoPropertyQueries.input ) { + logger.info( + `Found query configuration in the repository properties (${RepositoryPropertyName.EXTRA_QUERIES}): ` + + `${augmentationProperties.repoPropertyQueries.input.map((q) => q.uses).join(", ")}`, + ); + // If there are queries configured as a repository property, these may be organisational // settings. If they don't allow combining with other query configurations, return just the // ones configured in the repository properties. From 5a4aa832423ca0e9be11d01c46a54fffe81dfbfe Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Tue, 23 Sep 2025 09:49:29 +0100 Subject: [PATCH 23/23] Always log when combining queries is disabled in the repo properties --- lib/init-action.js | 8 +++----- src/config/db-config.ts | 11 ++++------- 2 files changed, 7 insertions(+), 12 deletions(-) diff --git a/lib/init-action.js b/lib/init-action.js index 8ff16f4540..0bc5e10eb7 100644 --- a/lib/init-action.js +++ b/lib/init-action.js @@ -86455,11 +86455,9 @@ function combineQueries(logger, config, augmentationProperties) { `Found query configuration in the repository properties (${"github-codeql-extra-queries" /* EXTRA_QUERIES */}): ${augmentationProperties.repoPropertyQueries.input.map((q) => q.uses).join(", ")}` ); if (!augmentationProperties.repoPropertyQueries.combines) { - if (!isDefaultSetup()) { - logger.info( - `Queries are configured in the repository properties and don't allow combining with other query settings. Any queries configured elsewhere will be ignored.` - ); - } + logger.info( + `The queries configured in the repository properties don't allow combining with other query settings. Any queries configured elsewhere will be ignored.` + ); return augmentationProperties.repoPropertyQueries.input; } else { result.push(...augmentationProperties.repoPropertyQueries.input); diff --git a/src/config/db-config.ts b/src/config/db-config.ts index 58e8cae45c..2639493543 100644 --- a/src/config/db-config.ts +++ b/src/config/db-config.ts @@ -2,7 +2,6 @@ import * as path from "path"; import * as semver from "semver"; -import { isDefaultSetup } from "../actions-util"; import * as errorMessages from "../error-messages"; import { RepositoryProperties, @@ -392,12 +391,10 @@ function combineQueries( // settings. If they don't allow combining with other query configurations, return just the // ones configured in the repository properties. if (!augmentationProperties.repoPropertyQueries.combines) { - if (!isDefaultSetup()) { - logger.info( - `Queries are configured in the repository properties and don't allow combining with other query settings. ` + - `Any queries configured elsewhere will be ignored.`, - ); - } + logger.info( + `The queries configured in the repository properties don't allow combining with other query settings. ` + + `Any queries configured elsewhere will be ignored.`, + ); return augmentationProperties.repoPropertyQueries.input; } else { // Otherwise, add them to the query array and continue.