Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 46 additions & 8 deletions lib/entry-points.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 6 additions & 3 deletions src/codeql.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import * as api from "./api-client";
import * as outputCache from "./cli/output-cache";
import type { VersionInfo } from "./cli/types";
import { CliError, wrapCliConfigurationError } from "./cli-errors";
import type { ActionConfig } from "./config/action-config";
import { appendExtraQueryExclusions, type Config } from "./config-utils";
import { DocUrl } from "./doc-url";
import { EnvVar, getEnv, exportEnvVar } from "./environment";
Expand Down Expand Up @@ -197,7 +198,7 @@ export interface CodeQL {
diagnosticsExport(
sarifFile: string,
automationDetailsId: string | undefined,
config: Config,
config: ActionConfig<"tempDir">,
): Promise<void>;
/** Get the location of an extractor for the specified language. */
resolveExtractor(language: Language): Promise<string>;
Expand Down Expand Up @@ -913,7 +914,7 @@ async function getCodeQLForCmd(
async diagnosticsExport(
sarifFile: string,
automationDetailsId: string | undefined,
config: Config,
config: ActionConfig<"tempDir">,
): Promise<void> {
const args = [
"diagnostics",
Expand Down Expand Up @@ -1237,7 +1238,9 @@ export async function getTrapCachingExtractorConfigArgsForLang(
*
* This will not exist if the configuration is being parsed in the Action.
*/
function getGeneratedCodeScanningConfigPath(config: Config): string {
function getGeneratedCodeScanningConfigPath(
config: ActionConfig<"tempDir">,
): string {
return path.resolve(config.tempDir, "user-config.yaml");
}

Expand Down
128 changes: 111 additions & 17 deletions src/config-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,11 @@ import {
import * as api from "./api-client";
import { getCachingKind } from "./caching-utils";
import { type CodeQL } from "./codeql";
import { type Config } from "./config/action-config";
import {
type AbortedInitConfig,
type ActionConfig,
type Config,
} from "./config/action-config";
import {
calculateAugmentation,
ExcludeQueryFilter,
Expand Down Expand Up @@ -71,6 +75,7 @@ import {
import { OverlayDatabaseMode } from "./overlay/overlay-database-mode";
import { shouldSkipOverlayAnalysis } from "./overlay/status";
import { RepositoryNwo } from "./repository";
import { ActionName } from "./status-report";
import { ToolsFeature } from "./tools-features";
import { downloadTrapCaches } from "./trap-caching";
import {
Expand Down Expand Up @@ -1379,31 +1384,84 @@ export function getPathToParsedConfigFile(tempDir: string): string {
return path.join(tempDir, "config");
}

function getPathToFailedConfigFile(tempDir: string): string {
return path.join(tempDir, "failed-config");
}

/**
* Store the given config to the path returned from getPathToParsedConfigFile.
* Writes {@link config} to {@link configFilePath}.
*
* This function is not intended to be called, other than by
* {@link saveConfig} and {@link saveFailedConfig} below.
Comment thread
mbg marked this conversation as resolved.
Outdated
*/
export async function saveConfig(config: Config, logger: Logger) {
function saveConfigFile(
logger: Logger,
configFilePath: string,
config: Partial<Config>,
) {
const configString = JSON.stringify(config);
const configFile = getPathToParsedConfigFile(config.tempDir);
fs.mkdirSync(path.dirname(configFile), { recursive: true });
fs.writeFileSync(configFile, configString, "utf8");
fs.mkdirSync(path.dirname(configFilePath), { recursive: true });
fs.writeFileSync(configFilePath, configString, "utf8");
logger.debug("Saved config:");
logger.debug(configString);
}

/**
* Get the config that has been saved to the given temp dir.
* If the config could not be found then returns undefined.
* Store the given config to the path returned from getPathToParsedConfigFile.
*/
export async function getConfig(
tempDir: string,
export async function saveConfig(config: Config, logger: Logger) {
const configFile = getPathToParsedConfigFile(config.tempDir);
saveConfigFile(logger, configFile, config);
}

/**
* Store parts of {@link config} as a failed configuration file.
*
* The file will only contain parts of {@link config} that are relevant to
* the `init-post` step in the workflow.
*/
export function saveAbortedInitConfig(
action: ActionState<["Base", "Logger"]>,
config: Config,
) {
// Should only be called by init.
if (action.name !== ActionName.Init) {
return;
}
Comment thread
mbg marked this conversation as resolved.

// Create a copy of the input `Config` that only contains the keys we want.
const failedConfig: AbortedInitConfig = {
version: config.version,
tempDir: config.tempDir,
codeQLCmd: config.codeQLCmd,
debugMode: config.debugMode,
gitHubVersion: config.gitHubVersion,
languages: config.languages,
analysisKinds: config.analysisKinds,
};

action.logger.debug(`Saving config for failed init step.`);
saveConfigFile(
action.logger,
getPathToFailedConfigFile(config.tempDir),
failedConfig,
);
}

/**
* Attempts to read a {@link Config} from {@link configFilePath}.
*
* This function is not intended to be called, other than by
* {@link getConfig} and {@link getAbortedInitConfig} below.
*/
function readConfigFile(
logger: Logger,
): Promise<Config | undefined> {
const configFile = getPathToParsedConfigFile(tempDir);
if (!fs.existsSync(configFile)) {
configFilePath: string,
): Config | undefined {
if (!fs.existsSync(configFilePath)) {
return undefined;
}
const configString = fs.readFileSync(configFile, "utf8");
const configString = fs.readFileSync(configFilePath, "utf8");
logger.debug("Loaded config:");
logger.debug(configString);

Expand All @@ -1428,6 +1486,36 @@ export async function getConfig(
return config as Config;
}

/**
* Get the config that has been saved to the given temp dir.
* If the config could not be found then returns undefined.
*/
export async function getConfig(
tempDir: string,
logger: Logger,
): Promise<Config | undefined> {
const configFile = getPathToParsedConfigFile(tempDir);
return readConfigFile(logger, configFile);
}

/**
* Loads a {@link AbortedInitConfig} from a failed `init` step if it exists.
*
* The result will only contain parts of a {@link Config} that are relevant to
* the `init-post` step in the workflow.
*/
export function getAbortedInitConfig(
action: ActionState<["Base", "Logger"]>,
tempDir: string,
): AbortedInitConfig | undefined {
// Only intended to be called by the `init-post` action.
if (action.name !== ActionName.InitPost) {
return undefined;
}
Comment thread
mbg marked this conversation as resolved.

return readConfigFile(action.logger, getPathToFailedConfigFile(tempDir));
}

/**
* Generate a `qlconfig.yml` file from the `registries` input.
* This file is used by the CodeQL CLI to list the registries to use for each
Expand Down Expand Up @@ -1618,21 +1706,27 @@ export function appendExtraQueryExclusions(
/**
* Returns `true` if Code Scanning analysis is enabled, or `false` if not.
*/
export function isCodeScanningEnabled(config: Config): boolean {
export function isCodeScanningEnabled(
config: ActionConfig<"analysisKinds">,
): boolean {
return config.analysisKinds.includes(AnalysisKind.CodeScanning);
}

/**
* Returns `true` if Code Quality analysis is enabled, or `false` if not.
*/
export function isCodeQualityEnabled(config: Config): boolean {
export function isCodeQualityEnabled(
config: ActionConfig<"analysisKinds">,
): boolean {
return config.analysisKinds.includes(AnalysisKind.CodeQuality);
}

/**
* Returns `true` if Code Scanning Risk Assessment analysis is enabled, or `false` if not.
*/
export function isRiskAssessmentEnabled(config: Config): boolean {
export function isRiskAssessmentEnabled(
config: ActionConfig<"analysisKinds">,
): boolean {
return config.analysisKinds.includes(AnalysisKind.RiskAssessment);
}

Expand Down
27 changes: 27 additions & 0 deletions src/config/action-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,3 +126,30 @@ export interface Config {
*/
enableFileCoverageInformation: boolean;
}

/** Properties available in `Config`. */
export type ActionConfigProperties = keyof Config;

/**
* The CodeQL Action configuration state that is persisted
* between steps of the CodeQL Action in a CodeQL workflow.
*
* By default, this is equivalent to `Config`. By explicitly
* providing required property names for `T` this can be restricted
* to requiring only those. For example, in `ActionConfig<"codeqlCmd">`
Comment thread
mbg marked this conversation as resolved.
Outdated
* all properties except `codeqlCmd` are optional.
*/
export type ActionConfig<

@henrymercer henrymercer Oct 12, 2026 •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bikeshed: ConfigWith?

Also, we should be careful with properties that might be optional, e.g. buildMode can be undefined in a successful configuration.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some time ago we talked about how Config is an awful name for the type and that it's really more representing the state of the Action that we persist across steps. It's also ambiguous as to whether it refers to the Action, CLI, or something else. I thought that I would use this opportunity to move away from that and introduce a better name.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My one comment about ActionConfig is that it's quite easy to confuse with ActionState, e.g. you mention "A new ActionState type is added" in the PR description. But I agree it's good to clarify that we're talking about Action configuration.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point and I have fixed that in the PR description now. I can't really think of a better name right now, but open to suggestions (and happy to rename as a follow-up).

T extends ActionConfigProperties = ActionConfigProperties,
> = Partial<Config> & Pick<Config, T>;

/** A minimal configuration that we can make available even when `init` aborts. */
export type AbortedInitConfig = ActionConfig<
| "version"
| "tempDir"
| "gitHubVersion"
| "debugMode"
| "languages"
| "analysisKinds"
| "codeQLCmd"
>;
Loading