diff --git a/lib/entry-points.js b/lib/entry-points.js index ab68cf74ee..53c94b78f5 100644 --- a/lib/entry-points.js +++ b/lib/entry-points.js @@ -146550,7 +146550,7 @@ async function sendStatusReport(statusReport) { ); } } -async function createInitWithConfigStatusReport(config, initStatusReport, configFile, totalCacheSize, overlayBaseDatabaseStats, dependencyCachingResults) { +async function createInitWithConfigStatusReport(config, initStatusReport, configFileInput, totalCacheSize, overlayBaseDatabaseStats, dependencyCachingResults) { const languages = config.languages.join(","); const paths = (config.originalUserInput.paths || []).join(","); const pathsIgnore = (config.originalUserInput["paths-ignore"] || []).join( @@ -146576,7 +146576,7 @@ async function createInitWithConfigStatusReport(config, initStatusReport, config } return { ...initStatusReport, - config_file: configFile ?? "", + config_file: configFileInput?.value ?? "", disable_default_queries: disableDefaultQueries, paths, paths_ignore: pathsIgnore, @@ -147996,13 +147996,20 @@ var stringProperty = { validate: isString2, parse: parseStringRepositoryProperty }; +function isNonEmptyString(value) { + return isString2(value) && value.trim().length > 0; +} +var nonEmptyStringProperty = { + ...stringProperty, + validate: isNonEmptyString +}; var booleanProperty = { // The value from the API should come as a string, which we then parse into a boolean. validate: isString2, parse: parseBooleanRepositoryProperty }; var repositoryPropertyParsers = { - ["github-codeql-config-file" /* CONFIG_FILE */]: stringProperty, + ["github-codeql-config-file" /* CONFIG_FILE */]: nonEmptyStringProperty, ["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, @@ -148423,6 +148430,50 @@ function parseUserConfig(logger, pathInput, contents, validateConfig) { } } +// src/config/inputs.ts +async function getComputedInput(action, repositoryProperties, name, options) { + const input = action.actions.getOptionalInput(name); + const propertyValue = repositoryProperties[options.repositoryPropertyName]; + if (options.repositoryPropertyFeatureEnabled && options.allowForcedRepositoryPropertyValue && propertyValue?.startsWith("!")) { + action.logger.info( + `Using ${name} input from repository property (enforced): ${propertyValue}` + ); + return { + // Drop the '!' from the value. + value: propertyValue.substring(1), + source: "repository-property" /* RepositoryProperty */ + }; + } + if (input !== void 0) { + action.logger.info(`Using ${name} input from workflow: ${input}`); + return { value: input, source: "workflow" /* Workflow */ }; + } + if (options.repositoryPropertyFeatureEnabled && propertyValue !== void 0) { + action.logger.info( + `Using ${name} input from repository property: ${propertyValue}` + ); + return { + value: propertyValue, + source: "repository-property" /* RepositoryProperty */ + }; + } else if (propertyValue !== void 0) { + action.logger.info( + `Ignoring ${name} input from repository property, because the corresponding feature flag is disabled.` + ); + } + return void 0; +} +async function getToolsInput(action, repositoryProperties) { + const allowRepositoryProperty = await action.features.getValue( + "tools_repository_property" /* ToolsRepositoryProperty */ + ); + return getComputedInput(action, repositoryProperties, "tools" /* Tools */, { + repositoryPropertyFeatureEnabled: allowRepositoryProperty, + allowForcedRepositoryPropertyValue: true, + repositoryPropertyName: "github-codeql-tools" /* TOOLS */ + }); +} + // src/config/remote-file.ts var DEFAULT_CONFIG_FILE_NAME = ".github/codeql-action.yaml"; var DEFAULT_CONFIG_FILE_REF = "main"; @@ -148496,33 +148547,15 @@ async function parseRemoteFileAddress(actionState, configFile) { // src/config/file.ts var LOCAL_PATH_PREFIX = "./"; var REMOTE_PATH_PREFIX = "remote="; -async function getConfigFileInput({ - logger, - actions, - features -}, 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 && propertyValue.trim().length > 0) { - const useRepositoryProperty = await features.getValue( - "config_file_repository_property" /* ConfigFileRepositoryProperty */ - ); - if (useRepositoryProperty) { - logger.info( - `Using configuration file input from repository property: ${propertyValue}` - ); - return propertyValue; - } else { - logger.info( - "Ignoring configuration file input from repository property, because the corresponding feature flag is disabled." - ); - } - } - return void 0; +async function getConfigFileInput(action, repositoryProperties) { + const useRepositoryProperty = await action.features.getValue( + "config_file_repository_property" /* ConfigFileRepositoryProperty */ + ); + return getComputedInput(action, repositoryProperties, "config-file" /* ConfigFile */, { + repositoryPropertyFeatureEnabled: useRepositoryProperty, + allowForcedRepositoryPropertyValue: false, + repositoryPropertyName: "github-codeql-config-file" /* CONFIG_FILE */ + }); } async function getRemoteConfig(actionState, configFile, apiDetails) { const address = await parseRemoteFileAddress(actionState, configFile); @@ -160347,40 +160380,6 @@ var core21 = __toESM(require_core()); var io7 = __toESM(require_io()); var semver10 = __toESM(require_semver2()); -// src/config/inputs.ts -async function getToolsInput(action, repositoryProperties) { - const name = "tools" /* Tools */; - const input = action.actions.getOptionalInput(name); - const propertyValue = repositoryProperties["github-codeql-tools" /* TOOLS */]; - const allowRepositoryProperty = await action.features.getValue( - "tools_repository_property" /* ToolsRepositoryProperty */ - ); - if (allowRepositoryProperty && propertyValue?.startsWith("!")) { - action.logger.info( - `Using ${name} input from repository property (enforced): ${propertyValue}` - ); - return { - // Drop the '!' from the value. - value: propertyValue.substring(1), - source: "repository-property" /* RepositoryProperty */ - }; - } - if (input !== void 0) { - action.logger.info(`Using ${name} input from workflow: ${input}`); - return { value: input, source: "workflow" /* Workflow */ }; - } - if (allowRepositoryProperty && propertyValue !== void 0) { - action.logger.info( - `Using ${name} input from repository property: ${propertyValue}` - ); - return { - value: propertyValue, - source: "repository-property" /* RepositoryProperty */ - }; - } - return void 0; -} - // src/workflow.ts var fs27 = __toESM(require("fs")); var path23 = __toESM(require("path")); @@ -160671,7 +160670,7 @@ async function sendStartingStatusReport(startedAt, config, logger) { await sendStatusReport(statusReportBase); } } -async function sendCompletedStatusReport2(startedAt, config, configFile, toolsInput, toolsDownloadStatusReport, toolsFeatureFlagsValid, toolsSource, toolsVersion, overlayBaseDatabaseStats, dependencyCachingResults, logger, error3) { +async function sendCompletedStatusReport2(startedAt, config, configFileInput, toolsInput, toolsDownloadStatusReport, toolsFeatureFlagsValid, toolsSource, toolsVersion, overlayBaseDatabaseStats, dependencyCachingResults, logger, error3) { const statusReportBase = await createStatusReportBase( "init" /* Init */, getActionsStatus(error3), @@ -160696,6 +160695,9 @@ async function sendCompletedStatusReport2(startedAt, config, configFile, toolsIn if (toolsInput !== void 0) { initStatusReport.computed_inputs.tools = toolsInput; } + if (configFileInput !== void 0) { + initStatusReport.computed_inputs["config-file"] = configFileInput; + } const initToolsDownloadFields = {}; if (toolsDownloadStatusReport?.downloadDurationMs !== void 0) { initToolsDownloadFields.tools_download_duration_ms = toolsDownloadStatusReport.downloadDurationMs; @@ -160707,7 +160709,7 @@ async function sendCompletedStatusReport2(startedAt, config, configFile, toolsIn const initWithConfigStatusReport = await createInitWithConfigStatusReport( config, initStatusReport, - configFile, + configFileInput, Math.round( await getTotalCacheSize(Object.values(config.trapCaches), logger) ), @@ -160727,7 +160729,7 @@ async function run3(actionState) { const logger = actionState.logger; let apiDetails; let config; - let configFile; + let configFileInput; let codeql; let features; let sourceRoot; @@ -160765,7 +160767,7 @@ async function run3(actionState) { core21.exportVariable("JOB_RUN_UUID" /* JOB_RUN_UUID */, jobRunUuid); core21.exportVariable("CODEQL_ACTION_INIT_HAS_RUN" /* INIT_ACTION_HAS_RUN */, "true"); const actionStateWithFeatures = { ...actionState, features }; - configFile = await getConfigFileInput( + configFileInput = await getConfigFileInput( actionStateWithFeatures, repositoryProperties ); @@ -160846,7 +160848,7 @@ async function run3(actionState) { packsInput: getOptionalInput("packs"), buildModeInput: getOptionalInput("build-mode"), ramInput: getOptionalInput("ram"), - configFile, + configFile: configFileInput?.value, dbLocation: getOptionalInput("db-location"), configInput: getOptionalInput("config"), dependencyCachingEnabled: getDependencyCachingEnabled(), @@ -161133,7 +161135,7 @@ exec ${goBinaryPath} "$@"` await sendCompletedStatusReport2( startedAt, config, - configFile, + configFileInput, toolsInput, toolsDownloadStatusReport, toolsFeatureFlagsValid, diff --git a/src/config/file.test.ts b/src/config/file.test.ts index 22e2f795f8..0659937ad3 100644 --- a/src/config/file.test.ts +++ b/src/config/file.test.ts @@ -13,6 +13,7 @@ import { } from "../testing-utils"; import { getConfigFileInput, getRemoteConfig } from "./file"; +import { InputSource } from "./inputs"; setupTests(test); @@ -41,8 +42,8 @@ test("getConfigFileInput returns input value", async (t) => { .returns(testInput); }) .withArgs(repositoryProperties) - .logs(t, "Using configuration file input from workflow") - .passes(t.is, testInput); + .logs(t, "Using config-file input from workflow") + .passes(t.deepEqual, { value: testInput, source: InputSource.Workflow }); }); test("getConfigFileInput returns repository property value", async (t) => { @@ -50,16 +51,11 @@ test("getConfigFileInput returns repository property value", async (t) => { await callee(getConfigFileInput) .withFeatures([Feature.ConfigFileRepositoryProperty]) .withArgs(repositoryProperties) - .logs(t, "Using configuration file input from repository property") - .passes(t.is, repositoryProperties[RepositoryPropertyName.CONFIG_FILE]); -}); - -test("getConfigFileInput ignores empty repository property value", async (t) => { - // Since the repository property value is an empty/whitespace string, we should ignore it. - await callee(getConfigFileInput) - .withFeatures([Feature.ConfigFileRepositoryProperty]) - .withArgs({ [RepositoryPropertyName.CONFIG_FILE]: " " }) - .passes(t.is, undefined); + .logs(t, "Using config-file input from repository property") + .passes(t.deepEqual, { + value: repositoryProperties[RepositoryPropertyName.CONFIG_FILE], + source: InputSource.RepositoryProperty, + }); }); test("getConfigFileInput ignores repository property value when FF is off", async (t) => { @@ -67,10 +63,10 @@ test("getConfigFileInput ignores repository property value when FF is off", asyn await callee(getConfigFileInput) .withFeatures([]) .withArgs(repositoryProperties) - .notLogs(t, "Using configuration file input from repository property") + .notLogs(t, "Using config-file input from repository property") .logs( t, - "Ignoring configuration file input from repository property, because the corresponding feature flag is disabled.", + "Ignoring config-file input from repository property, because the corresponding feature flag is disabled.", ) .passes(t.is, undefined); }); diff --git a/src/config/file.ts b/src/config/file.ts index 8cb7bc3a11..5cb9d1993b 100644 --- a/src/config/file.ts +++ b/src/config/file.ts @@ -9,6 +9,7 @@ import { import { ConfigurationError } from "../util"; import { parseUserConfig, UserConfig } from "./db-config"; +import { getComputedInput, InputName, type ComputedInput } from "./inputs"; import { parseRemoteFileAddress } from "./remote-file"; /** @@ -28,42 +29,19 @@ export const REMOTE_PATH_PREFIX = "remote="; * Gets the value that is configured for the configuration file, if any. */ export async function getConfigFileInput( - { - logger, - actions, - features, - }: ActionState<["Logger", "Actions", "FeatureFlags"]>, + action: ActionState<["Logger", "Actions", "FeatureFlags"]>, repositoryProperties: Partial, -): Promise { - 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 && propertyValue.trim().length > 0) { - // Only use the repository property value if the FF is enabled. - const useRepositoryProperty = await features.getValue( - Feature.ConfigFileRepositoryProperty, - ); - - if (useRepositoryProperty) { - logger.info( - `Using configuration file input from repository property: ${propertyValue}`, - ); - return propertyValue; - } else { - logger.info( - "Ignoring configuration file input from repository property, because the corresponding feature flag is disabled.", - ); - } - } +): Promise { + // Only use the repository property value if the FF is enabled. + const useRepositoryProperty = await action.features.getValue( + Feature.ConfigFileRepositoryProperty, + ); - return undefined; + return getComputedInput(action, repositoryProperties, InputName.ConfigFile, { + repositoryPropertyFeatureEnabled: useRepositoryProperty, + allowForcedRepositoryPropertyValue: false, + repositoryPropertyName: RepositoryPropertyName.CONFIG_FILE, + }); } /** diff --git a/src/config/inputs.ts b/src/config/inputs.ts index 32a8dfd6f6..d027e2a42e 100644 --- a/src/config/inputs.ts +++ b/src/config/inputs.ts @@ -3,10 +3,12 @@ import { Feature } from "../feature-flags"; import { RepositoryProperties, RepositoryPropertyName, + StringRepositoryPropertyNames, } from "../feature-flags/properties"; /** Enumerates input names. */ export enum InputName { + ConfigFile = "config-file", Tools = "tools", } @@ -28,26 +30,50 @@ export type ComputedInput = { }; /** - * Gets the computed `tools` input. This comes from either the workflow or + * Represents options for how to compute an input. + */ +export interface ComputedInputOptions { + /** + * Whether the FF for the repository property (if any) is enabled. + */ + repositoryPropertyFeatureEnabled: boolean; + /** + * The name of the repository property to try and get the input value from. + */ + repositoryPropertyName: StringRepositoryPropertyNames; + /** + * Whether the repository property value may start with `!` to take precedence + * over any input value provided in the workflow file. + */ + allowForcedRepositoryPropertyValue: boolean; +} + +/** + * Gets the computed input for `name`. This comes from either the workflow or * the repository property. * * @param action The Action state. * @param repositoryProperties The values of known repository properties. + * @param name The name of the input to compute. + * @param options Options for how to compute the input value. + * * @returns The computed input or `undefined` if there is no input. */ -export async function getToolsInput( +export async function getComputedInput( action: ActionState<["Logger", "Actions", "FeatureFlags"]>, - repositoryProperties: Partial, + repositoryProperties: RepositoryProperties, + name: InputName, + options: ComputedInputOptions, ): Promise { - const name = InputName.Tools; const input = action.actions.getOptionalInput(name); - const propertyValue = repositoryProperties[RepositoryPropertyName.TOOLS]; - const allowRepositoryProperty = await action.features.getValue( - Feature.ToolsRepositoryProperty, - ); + const propertyValue = repositoryProperties[options.repositoryPropertyName]; // The repository property takes precedence if it starts with an '!'. - if (allowRepositoryProperty && propertyValue?.startsWith("!")) { + if ( + options.repositoryPropertyFeatureEnabled && + options.allowForcedRepositoryPropertyValue && + propertyValue?.startsWith("!") + ) { action.logger.info( `Using ${name} input from repository property (enforced): ${propertyValue}`, ); @@ -65,7 +91,7 @@ export async function getToolsInput( } // Use the repository property if there's no workflow input. - if (allowRepositoryProperty && propertyValue !== undefined) { + if (options.repositoryPropertyFeatureEnabled && propertyValue !== undefined) { action.logger.info( `Using ${name} input from repository property: ${propertyValue}`, ); @@ -73,8 +99,34 @@ export async function getToolsInput( value: propertyValue, source: InputSource.RepositoryProperty, }; + } else if (propertyValue !== undefined) { + action.logger.info( + `Ignoring ${name} input from repository property, because the corresponding feature flag is disabled.`, + ); } // There's no input. return undefined; } + +/** + * Gets the computed `tools` input. This comes from either the workflow or + * the repository property. + * + * @param action The Action state. + * @param repositoryProperties The values of known repository properties. + * @returns The computed input or `undefined` if there is no input. + */ +export async function getToolsInput( + action: ActionState<["Logger", "Actions", "FeatureFlags"]>, + repositoryProperties: Partial, +): Promise { + const allowRepositoryProperty = await action.features.getValue( + Feature.ToolsRepositoryProperty, + ); + return getComputedInput(action, repositoryProperties, InputName.Tools, { + repositoryPropertyFeatureEnabled: allowRepositoryProperty, + allowForcedRepositoryPropertyValue: true, + repositoryPropertyName: RepositoryPropertyName.TOOLS, + }); +} diff --git a/src/feature-flags/properties.ts b/src/feature-flags/properties.ts index 4c888bd5ec..4ee487728f 100644 --- a/src/feature-flags/properties.ts +++ b/src/feature-flags/properties.ts @@ -29,6 +29,13 @@ export type AllRepositoryProperties = { [RepositoryPropertyName.TOOLS]: string; }; +/** The subset of known repository properties which are of type `string`. */ +export type StringRepositoryPropertyNames = keyof { + [K in keyof AllRepositoryProperties as AllRepositoryProperties[K] extends string + ? K + : never]: AllRepositoryProperties[K]; +}; + /** Parsed repository properties. */ export type RepositoryProperties = Partial; @@ -72,6 +79,17 @@ const stringProperty = { parse: parseStringRepositoryProperty, }; +/** Determines whether a value from the API is a non-empty string or not. */ +function isNonEmptyString(value: RepositoryPropertyValue): value is string { + return isString(value) && value.trim().length > 0; +} + +/** A repository property that we expect to contain a non-empty string value. */ +const nonEmptyStringProperty = { + ...stringProperty, + validate: isNonEmptyString, +}; + /** A repository property that we expect to contain a boolean value. */ const booleanProperty = { // The value from the API should come as a string, which we then parse into a boolean. @@ -83,7 +101,7 @@ const booleanProperty = { const repositoryPropertyParsers: { [K in RepositoryPropertyName]: PropertyInfo; } = { - [RepositoryPropertyName.CONFIG_FILE]: stringProperty, + [RepositoryPropertyName.CONFIG_FILE]: nonEmptyStringProperty, [RepositoryPropertyName.DISABLE_OVERLAY]: booleanProperty, [RepositoryPropertyName.EXTRA_QUERIES]: stringProperty, [RepositoryPropertyName.FILE_COVERAGE_ON_PRS]: booleanProperty, diff --git a/src/init-action.ts b/src/init-action.ts index c7837c6eed..4e59e740de 100644 --- a/src/init-action.ts +++ b/src/init-action.ts @@ -129,7 +129,7 @@ async function sendStartingStatusReport( async function sendCompletedStatusReport( startedAt: Date, config: configUtils.Config | undefined, - configFile: string | undefined, + configFileInput: ComputedInput | undefined, toolsInput: ComputedInput | undefined, toolsDownloadStatusReport: ToolsDownloadStatusReport | undefined, toolsFeatureFlagsValid: boolean | undefined, @@ -168,6 +168,9 @@ async function sendCompletedStatusReport( if (toolsInput !== undefined) { initStatusReport.computed_inputs.tools = toolsInput; } + if (configFileInput !== undefined) { + initStatusReport.computed_inputs["config-file"] = configFileInput; + } const initToolsDownloadFields: InitToolsDownloadFields = {}; @@ -185,7 +188,7 @@ async function sendCompletedStatusReport( await createInitWithConfigStatusReport( config, initStatusReport, - configFile, + configFileInput, Math.round( await getTotalCacheSize(Object.values(config.trapCaches), logger), ), @@ -212,7 +215,7 @@ async function run( let apiDetails: GitHubApiCombinedDetails; let config: configUtils.Config | undefined; - let configFile: string | undefined; + let configFileInput: ComputedInput | undefined; let codeql: CodeQL; let features: FeatureEnablement; let sourceRoot: string; @@ -263,7 +266,7 @@ async function run( core.exportVariable(EnvVar.INIT_ACTION_HAS_RUN, "true"); const actionStateWithFeatures = { ...actionState, features }; - configFile = await getConfigFileInput( + configFileInput = await getConfigFileInput( actionStateWithFeatures, repositoryProperties, ); @@ -376,7 +379,7 @@ async function run( packsInput: getOptionalInput("packs"), buildModeInput: getOptionalInput("build-mode"), ramInput: getOptionalInput("ram"), - configFile, + configFile: configFileInput?.value, dbLocation: getOptionalInput("db-location"), configInput: getOptionalInput("config"), dependencyCachingEnabled: getDependencyCachingEnabled(), @@ -782,7 +785,7 @@ async function run( await sendCompletedStatusReport( startedAt, config, - configFile, + configFileInput, toolsInput, toolsDownloadStatusReport, toolsFeatureFlagsValid, diff --git a/src/status-report.ts b/src/status-report.ts index d9d2a7ba4c..753ac78693 100644 --- a/src/status-report.ts +++ b/src/status-report.ts @@ -554,7 +554,7 @@ export interface InitToolsDownloadFields { * * @param config The CodeQL Action configuration whose values should be added to the base status report. * @param initStatusReport The base status report. - * @param configFile Optionally, the filename of the configuration file that was read. + * @param configFileInput Optionally, the filename of the configuration file that was read. * @param totalCacheSize The computed total TRAP cache size. * @param overlayBaseDatabaseStats Statistics about the overlay database, if any. * @returns @@ -562,7 +562,7 @@ export interface InitToolsDownloadFields { export async function createInitWithConfigStatusReport( config: Config, initStatusReport: InitStatusReport, - configFile: string | undefined, + configFileInput: ComputedInput | undefined, totalCacheSize: number, overlayBaseDatabaseStats: OverlayBaseDatabaseDownloadStats | undefined, dependencyCachingResults: DependencyCacheRestoreStatusReport | undefined, @@ -601,7 +601,7 @@ export async function createInitWithConfigStatusReport( return { ...initStatusReport, - config_file: configFile ?? "", + config_file: configFileInput?.value ?? "", disable_default_queries: disableDefaultQueries, paths, paths_ignore: pathsIgnore,