mirror of
https://github.com/github/codeql-action.git
synced 2026-10-14 14:51:36 +00:00
Compare commits
4 Commits
mbg/use-gi
...
mbg/config
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
af8f50bf94 | ||
|
|
f638e8b5fb | ||
|
|
391a05d414 | ||
|
|
fdf46cd70c |
43
lib/entry-points.js
generated
43
lib/entry-points.js
generated
@@ -146993,6 +146993,11 @@ var featureConfig = {
|
||||
envVar: "CODEQL_ACTION_PROXY_API_REQUESTS",
|
||||
minimumVersion: void 0
|
||||
},
|
||||
["scoped_configurations" /* ScopedConfigurations */]: {
|
||||
defaultValue: false,
|
||||
envVar: "CODEQL_ACTION_SCOPED_CONFIGURATIONS",
|
||||
minimumVersion: void 0
|
||||
},
|
||||
["skip_file_coverage_on_prs" /* SkipFileCoverageOnPrs */]: {
|
||||
defaultValue: false,
|
||||
envVar: "CODEQL_ACTION_SKIP_FILE_COVERAGE_ON_PRS",
|
||||
@@ -148110,6 +148115,24 @@ var ORG_SCHEMA = {
|
||||
var DEFAULT_SETUP_SCHEMA = {
|
||||
org: optional(object(ORG_SCHEMA))
|
||||
};
|
||||
function hasAnalysisKindKey(analysisKind, config) {
|
||||
return analysisKind in config && typeof config[analysisKind] === "object";
|
||||
}
|
||||
function removeAnalysisKindConfigs(config) {
|
||||
const configWithoutScopes = { ...config };
|
||||
for (const kind of Object.values(AnalysisKind)) {
|
||||
delete configWithoutScopes[kind];
|
||||
}
|
||||
return configWithoutScopes;
|
||||
}
|
||||
function applyAnalysisKindConfig(analysisKind, config) {
|
||||
const configWithoutScopes = removeAnalysisKindConfigs(config);
|
||||
if (!hasAnalysisKindKey(analysisKind, config)) {
|
||||
return configWithoutScopes;
|
||||
}
|
||||
const analysisKindSpecificConfig = config[analysisKind];
|
||||
return { ...configWithoutScopes, ...analysisKindSpecificConfig };
|
||||
}
|
||||
var DEFAULT_SETUP_CONFIG_SCHEMA = {
|
||||
"threat-models": optional(array(string)),
|
||||
"default-setup": optional(
|
||||
@@ -149386,7 +149409,8 @@ async function downloadCacheWithTime(codeQL, languages, logger) {
|
||||
const trapCacheDownloadTime = import_perf_hooks.performance.now() - start;
|
||||
return { trapCaches, trapCacheDownloadTime };
|
||||
}
|
||||
async function loadUserConfig(actionState, configFile, workspacePath, apiDetails, tempDir) {
|
||||
async function loadUserConfig(actionState, analysisKinds, configFile, workspacePath, apiDetails, tempDir) {
|
||||
let config;
|
||||
if (isLocal(configFile)) {
|
||||
if (configFile !== userConfigFromActionPath(tempDir)) {
|
||||
configFile = path10.resolve(workspacePath, configFile);
|
||||
@@ -149399,13 +149423,24 @@ async function loadUserConfig(actionState, configFile, workspacePath, apiDetails
|
||||
const validateConfig = await actionState.features.getValue(
|
||||
"validate_db_config" /* ValidateDbConfig */
|
||||
);
|
||||
return getLocalConfig(actionState.logger, configFile, validateConfig);
|
||||
config = getLocalConfig(actionState.logger, configFile, validateConfig);
|
||||
} else {
|
||||
if (isExplicitRemotePath(configFile)) {
|
||||
configFile = configFile.substring(REMOTE_PATH_PREFIX.length);
|
||||
}
|
||||
return await getRemoteConfig(actionState, configFile, apiDetails);
|
||||
config = await getRemoteConfig(actionState, configFile, apiDetails);
|
||||
}
|
||||
const allowScopedConfig = await actionState.features.getValue(
|
||||
"scoped_configurations" /* ScopedConfigurations */
|
||||
);
|
||||
if (allowScopedConfig && analysisKinds.length === 1) {
|
||||
return applyAnalysisKindConfig(analysisKinds[0], config);
|
||||
} else if (allowScopedConfig) {
|
||||
actionState.logger.info(
|
||||
`Ignoring '${"scoped_configurations" /* ScopedConfigurations */}' feature, because multiple analysis kinds are enabled.`
|
||||
);
|
||||
}
|
||||
return removeAnalysisKindConfigs(config);
|
||||
}
|
||||
var OVERLAY_ANALYSIS_FEATURES = {
|
||||
cpp: "overlay_analysis_cpp" /* OverlayAnalysisCpp */,
|
||||
@@ -149706,6 +149741,7 @@ async function determineUserConfig(action, tempDir, inputs) {
|
||||
);
|
||||
const fromConfigFile = await loadUserConfig(
|
||||
action,
|
||||
inputs.analysisKinds,
|
||||
inputs.configFile,
|
||||
inputs.workspacePath,
|
||||
inputs.apiDetails,
|
||||
@@ -149742,6 +149778,7 @@ async function determineUserConfig(action, tempDir, inputs) {
|
||||
action.logger.debug(`Using configuration file: ${inputs.configFile}`);
|
||||
return await loadUserConfig(
|
||||
action,
|
||||
inputs.analysisKinds,
|
||||
inputs.configFile,
|
||||
inputs.workspacePath,
|
||||
inputs.apiDetails,
|
||||
|
||||
@@ -12,7 +12,7 @@ import { AnalysisKind, supportedAnalysisKinds } from "./analyses";
|
||||
import * as api from "./api-client";
|
||||
import { CachingKind } from "./caching-utils";
|
||||
import { createStubCodeQL } from "./codeql";
|
||||
import { UserConfig } from "./config/db-config";
|
||||
import { UserConfig, UserConfigWithActionExtensions } from "./config/db-config";
|
||||
import * as file from "./config/file";
|
||||
import * as configUtils from "./config-utils";
|
||||
import * as errorMessages from "./error-messages";
|
||||
@@ -2544,6 +2544,7 @@ test("loadUserConfig - loads local configuration files", async (t) => {
|
||||
) =>
|
||||
configUtils.loadUserConfig(
|
||||
actionState,
|
||||
[AnalysisKind.CodeScanning],
|
||||
filePath,
|
||||
workspaceDir,
|
||||
SAMPLE_DOTCOM_API_DETAILS,
|
||||
@@ -2587,7 +2588,13 @@ test.serial("loadUserConfig - loads remote configuration files", async (t) => {
|
||||
|
||||
const remoteAddress = "owner/repo/file@ref";
|
||||
await callee(configUtils.loadUserConfig)
|
||||
.withArgs(remoteAddress, tmpDir, SAMPLE_DOTCOM_API_DETAILS, tmpDir)
|
||||
.withArgs(
|
||||
[AnalysisKind.CodeScanning],
|
||||
remoteAddress,
|
||||
tmpDir,
|
||||
SAMPLE_DOTCOM_API_DETAILS,
|
||||
tmpDir,
|
||||
)
|
||||
.passes(t.deepEqual, {});
|
||||
|
||||
t.true(
|
||||
@@ -2665,6 +2672,7 @@ test.serial(
|
||||
|
||||
// Prepare the test call to `loadUserConfig`.
|
||||
const targetWithArgs = target.withArgs(
|
||||
[AnalysisKind.CodeScanning],
|
||||
address,
|
||||
tmpDir,
|
||||
SAMPLE_DOTCOM_API_DETAILS,
|
||||
@@ -2705,3 +2713,57 @@ test.serial(
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
test("loadUserConfig - applies analysis-specific settings", async (t) => {
|
||||
await withTmpDir(async (workspaceDir) => {
|
||||
await withTmpDir(async (tmpDir) => {
|
||||
// Construct the test target.
|
||||
const loadUserConfig = (
|
||||
actionState: ActionState<["Logger", "Env", "FeatureFlags"]>,
|
||||
filePath: string,
|
||||
analysisKind: AnalysisKind,
|
||||
) =>
|
||||
configUtils.loadUserConfig(
|
||||
actionState,
|
||||
[analysisKind],
|
||||
filePath,
|
||||
workspaceDir,
|
||||
SAMPLE_DOTCOM_API_DETAILS,
|
||||
tmpDir,
|
||||
);
|
||||
const target = callee(loadUserConfig);
|
||||
|
||||
for (const analysisKind of Object.values(AnalysisKind)) {
|
||||
const analysisSpecificConfig: UserConfig = {
|
||||
paths: ["custom"],
|
||||
queries: [{ uses: "foo" }],
|
||||
};
|
||||
const baseConfig: UserConfig = { "threat-models": ["remote"] };
|
||||
|
||||
// Write a local configuration file with an `analysisKind`-specific section.
|
||||
const configPath = path.join(workspaceDir, "codeql.yml");
|
||||
fs.writeFileSync(
|
||||
configPath,
|
||||
JSON.stringify({
|
||||
...baseConfig,
|
||||
[analysisKind]: analysisSpecificConfig,
|
||||
} satisfies UserConfigWithActionExtensions),
|
||||
"utf8",
|
||||
);
|
||||
|
||||
// The resulting configuration should be the `baseConfig` with the `analysisSpecificConfig`
|
||||
// merged into it if the FF is enabled.
|
||||
await target
|
||||
.withArgs(configPath, analysisKind)
|
||||
.withFeatures([Feature.ScopedConfigurations])
|
||||
.passes(t.deepEqual, { ...baseConfig, ...analysisSpecificConfig });
|
||||
|
||||
// Without the FF, it should be the `baseConfig`. This also validates that the
|
||||
// `AnalysisKind`-specific sections are removed even if the FF is off.
|
||||
await target
|
||||
.withArgs(configPath, analysisKind)
|
||||
.passes(t.deepEqual, baseConfig);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -24,11 +24,13 @@ import { getCachingKind } from "./caching-utils";
|
||||
import { type CodeQL } from "./codeql";
|
||||
import { type Config } from "./config/action-config";
|
||||
import {
|
||||
applyAnalysisKindConfig,
|
||||
calculateAugmentation,
|
||||
ExcludeQueryFilter,
|
||||
generateCodeScanningConfig,
|
||||
mergeDefaultSetupAndUserConfigs,
|
||||
parseUserConfig,
|
||||
removeAnalysisKindConfigs,
|
||||
UserConfig,
|
||||
} from "./config/db-config";
|
||||
import {
|
||||
@@ -484,11 +486,14 @@ async function downloadCacheWithTime(
|
||||
*/
|
||||
export async function loadUserConfig(
|
||||
actionState: ActionState<["Logger", "Env", "FeatureFlags"]>,
|
||||
analysisKinds: AnalysisKind[],
|
||||
configFile: string,
|
||||
workspacePath: string,
|
||||
apiDetails: api.GitHubApiCombinedDetails,
|
||||
tempDir: string,
|
||||
): Promise<UserConfig> {
|
||||
let config: UserConfig;
|
||||
|
||||
if (isLocal(configFile)) {
|
||||
if (configFile !== userConfigFromActionPath(tempDir)) {
|
||||
// If the config file is not generated by the Action, it should be relative to the workspace.
|
||||
@@ -503,7 +508,7 @@ export async function loadUserConfig(
|
||||
const validateConfig = await actionState.features.getValue(
|
||||
Feature.ValidateDbConfig,
|
||||
);
|
||||
return getLocalConfig(actionState.logger, configFile, validateConfig);
|
||||
config = getLocalConfig(actionState.logger, configFile, validateConfig);
|
||||
} else {
|
||||
// Drop the explicit prefix if it is present. Since `REMOTE_PATH_PREFIX` is chosen
|
||||
// to not conflict with permissible characters in "owner" or "repo" components,
|
||||
@@ -511,8 +516,29 @@ export async function loadUserConfig(
|
||||
if (isExplicitRemotePath(configFile)) {
|
||||
configFile = configFile.substring(REMOTE_PATH_PREFIX.length);
|
||||
}
|
||||
return await getRemoteConfig(actionState, configFile, apiDetails);
|
||||
config = await getRemoteConfig(actionState, configFile, apiDetails);
|
||||
}
|
||||
|
||||
// If the `ScopedConfigurations` feature is enabled, we allow options specific to the
|
||||
// current `AnalysisKind` to be applied on top of a base configuration.
|
||||
const allowScopedConfig = await actionState.features.getValue(
|
||||
Feature.ScopedConfigurations,
|
||||
);
|
||||
|
||||
if (allowScopedConfig && analysisKinds.length === 1) {
|
||||
// Apply the `analysisKind`-specific configuration.
|
||||
return applyAnalysisKindConfig(analysisKinds[0], config);
|
||||
} else if (allowScopedConfig) {
|
||||
actionState.logger.info(
|
||||
`Ignoring '${Feature.ScopedConfigurations}' feature, because multiple analysis kinds are enabled.`,
|
||||
);
|
||||
}
|
||||
|
||||
// Remove `AnalysisKind`-specific sections from `config` even if the FF is not enabled,
|
||||
// because the CLI will warn about unknown keys in the configuration. This is to guard
|
||||
// against the case where we ship the feature, enable the FF, start using the new keys,
|
||||
// and then need to roll the FF back.
|
||||
return removeAnalysisKindConfigs(config);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1071,6 +1097,7 @@ export async function determineUserConfig(
|
||||
);
|
||||
const fromConfigFile = await loadUserConfig(
|
||||
action,
|
||||
inputs.analysisKinds,
|
||||
inputs.configFile,
|
||||
inputs.workspacePath,
|
||||
inputs.apiDetails,
|
||||
@@ -1118,6 +1145,7 @@ export async function determineUserConfig(
|
||||
action.logger.debug(`Using configuration file: ${inputs.configFile}`);
|
||||
return await loadUserConfig(
|
||||
action,
|
||||
inputs.analysisKinds,
|
||||
inputs.configFile,
|
||||
inputs.workspacePath,
|
||||
inputs.apiDetails,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import test, { ExecutionContext } from "ava";
|
||||
|
||||
import { AnalysisKind } from "../analyses";
|
||||
import { RepositoryProperties } from "../feature-flags/properties";
|
||||
import { BuiltInLanguage, Language } from "../languages";
|
||||
import { getRunnerLogger } from "../logging";
|
||||
@@ -14,6 +15,117 @@ import { ConfigurationError, prettyPrintPack } from "../util";
|
||||
|
||||
import * as dbConfig from "./db-config";
|
||||
|
||||
function makeUserConfigWithActionExtensions(options?: {
|
||||
analysisConfig?: dbConfig.UserConfig;
|
||||
analysisKinds?: AnalysisKind[];
|
||||
baseConfig?: dbConfig.UserConfig;
|
||||
}) {
|
||||
const testConfig = { ...options?.baseConfig };
|
||||
|
||||
const allAnalysisKinds = Object.values(AnalysisKind);
|
||||
const analysisKinds = options?.analysisKinds ?? allAnalysisKinds;
|
||||
for (const analysisKind of analysisKinds) {
|
||||
testConfig[analysisKind] = { ...options?.analysisConfig };
|
||||
}
|
||||
|
||||
return testConfig as dbConfig.UserConfigWithActionExtensions;
|
||||
}
|
||||
|
||||
test("hasAnalysisKindKey - finds `AnalysisKind` sections", async (t) => {
|
||||
const testConfig = makeUserConfigWithActionExtensions();
|
||||
|
||||
for (const analysisKind of Object.values(AnalysisKind)) {
|
||||
// Check that it finds the `analysisKind`-specific section among multiple.
|
||||
t.true(dbConfig.hasAnalysisKindKey(analysisKind, testConfig));
|
||||
// And for just the `analysisKind`-specific section.
|
||||
t.true(dbConfig.hasAnalysisKindKey(analysisKind, { [analysisKind]: {} }));
|
||||
}
|
||||
});
|
||||
|
||||
test("hasAnalysisKindKey - returns false if there is no relevant section", async (t) => {
|
||||
const testConfig = makeUserConfigWithActionExtensions();
|
||||
|
||||
for (const analysisKind of Object.values(AnalysisKind)) {
|
||||
// Clone the `testConfig` and remove the `analysisKind`-specific key.
|
||||
const testConfigClone = { ...testConfig };
|
||||
delete testConfigClone[analysisKind];
|
||||
|
||||
// Check that it doesn't find an `analysisKind`-specific section.
|
||||
t.false(dbConfig.hasAnalysisKindKey(analysisKind, testConfigClone));
|
||||
}
|
||||
});
|
||||
|
||||
test("applyAnalysisKindConfig - applies analysis-specific settings", async (t) => {
|
||||
for (const analysisKind of Object.values(AnalysisKind)) {
|
||||
const testConfig = makeUserConfigWithActionExtensions({
|
||||
analysisKinds: [analysisKind],
|
||||
analysisConfig: {
|
||||
"paths-ignore": [],
|
||||
},
|
||||
baseConfig: {
|
||||
"paths-ignore": ["a", "b", "c"],
|
||||
"threat-models": ["local"],
|
||||
},
|
||||
});
|
||||
const clonedTestConfig = { ...testConfig };
|
||||
|
||||
const result = dbConfig.applyAnalysisKindConfig(analysisKind, testConfig);
|
||||
|
||||
// The `analysisKind`-specific section should have been removed.
|
||||
// `threat-models` should be unchanged from the `baseConfig`.
|
||||
// `paths-ignore` should have been overwritten with the `analysisKind`-specific config.
|
||||
t.deepEqual(result, {
|
||||
"threat-models": ["local"],
|
||||
"paths-ignore": [],
|
||||
});
|
||||
|
||||
// The input `testConfig` should not have changed.
|
||||
t.deepEqual(testConfig, clonedTestConfig);
|
||||
}
|
||||
});
|
||||
|
||||
test("applyAnalysisKindConfig - removes all `AnalysisKind`-specific keys", async (t) => {
|
||||
for (const analysisKind of Object.values(AnalysisKind)) {
|
||||
const testConfig = makeUserConfigWithActionExtensions({
|
||||
analysisConfig: {
|
||||
"paths-ignore": ["a", "b", "c"],
|
||||
},
|
||||
baseConfig: {},
|
||||
});
|
||||
|
||||
const result = dbConfig.applyAnalysisKindConfig(analysisKind, testConfig);
|
||||
|
||||
// All `AnalysisKind`-specific sections should have been removed.
|
||||
// `paths-ignore` should have been overwritten with the analysis-specific config.
|
||||
t.deepEqual(result, {
|
||||
"paths-ignore": ["a", "b", "c"],
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
test("applyAnalysisKindConfig - returns the `baseConfig` if there is no `analysisKind`-specific key", async (t) => {
|
||||
const baseConfig = {
|
||||
"disable-default-queries": true,
|
||||
"paths-ignore": ["a", "b", "c"],
|
||||
queries: [{ name: "foo", uses: "something" }],
|
||||
packs: { test: ["bar"] },
|
||||
"default-setup": { org: { "model-packs": ["pack"] } },
|
||||
} satisfies dbConfig.UserConfig;
|
||||
|
||||
const testConfig = makeUserConfigWithActionExtensions({
|
||||
analysisKinds: [],
|
||||
baseConfig,
|
||||
});
|
||||
|
||||
const result = dbConfig.applyAnalysisKindConfig(
|
||||
AnalysisKind.CodeScanning,
|
||||
testConfig,
|
||||
);
|
||||
|
||||
// The result should be the same as the `baseConfig`.
|
||||
t.deepEqual(result, baseConfig);
|
||||
});
|
||||
|
||||
/**
|
||||
* Test macro for ensuring the packs block is valid
|
||||
*/
|
||||
|
||||
@@ -4,6 +4,7 @@ import * as yaml from "js-yaml";
|
||||
import * as jsonschema from "jsonschema";
|
||||
import * as semver from "semver";
|
||||
|
||||
import { AnalysisKind } from "../analyses";
|
||||
import {
|
||||
addNoLanguageDiagnostic,
|
||||
makeTelemetryDiagnostic,
|
||||
@@ -77,6 +78,68 @@ export interface UserConfig {
|
||||
"default-setup"?: DefaultSetupConfig;
|
||||
}
|
||||
|
||||
/** Represents a `UserConfig` with CodeQL Action extensions. */
|
||||
export type UserConfigWithActionExtensions = {
|
||||
/**
|
||||
* We add a key for each analysis kind that may contain options
|
||||
* specific to that kind of analysis.
|
||||
*/
|
||||
[T in keyof typeof AnalysisKind as (typeof AnalysisKind)[T]]?: UserConfig;
|
||||
} & UserConfig;
|
||||
|
||||
/**
|
||||
* Determines whether `config` contains an `analysisKind`-specific section.
|
||||
*
|
||||
* @param analysisKind The analysis section to check for.
|
||||
* @param config The base `UserConfig` value.
|
||||
*/
|
||||
export function hasAnalysisKindKey(
|
||||
analysisKind: AnalysisKind,
|
||||
config: UserConfig,
|
||||
): config is UserConfigWithActionExtensions {
|
||||
return analysisKind in config && typeof config[analysisKind] === "object";
|
||||
}
|
||||
|
||||
/**
|
||||
* Clones `config` and removes any `AnalysisKind`-specific sections.
|
||||
*/
|
||||
export function removeAnalysisKindConfigs(config: UserConfig): UserConfig {
|
||||
// Clone the configuration and remove any `AnalysisKind`-specific sections.
|
||||
const configWithoutScopes = { ...config };
|
||||
for (const kind of Object.values(AnalysisKind)) {
|
||||
delete configWithoutScopes[kind];
|
||||
}
|
||||
return configWithoutScopes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies an `analysisKind`-specific configuration, if any, to the resulting configuration
|
||||
* and removes all `AnalysisKind`-specific configuration sections.
|
||||
*
|
||||
* @param analysisKind The `AnalysisKind` to apply specific configuration options for.
|
||||
* @param config The loaded configuration, possibly including `analysisKind`-specific settings.
|
||||
*/
|
||||
export function applyAnalysisKindConfig(
|
||||
analysisKind: AnalysisKind,
|
||||
config: UserConfig,
|
||||
): UserConfig {
|
||||
// Clone the configuration and remove any `AnalysisKind`-specific sections.
|
||||
const configWithoutScopes = removeAnalysisKindConfigs(config);
|
||||
|
||||
// If there is no `analysisKind`-specific section, return the input configuration
|
||||
// without any `AnalysisKind`-specific sections.
|
||||
if (!hasAnalysisKindKey(analysisKind, config)) {
|
||||
return configWithoutScopes;
|
||||
}
|
||||
|
||||
// Otherwise, extract the `analysisKind`-specific configuration from the input `config`.
|
||||
const analysisKindSpecificConfig = config[analysisKind];
|
||||
|
||||
// Return the base configuration (without any `AnalysisKind`-specific sections) and
|
||||
// override the top-level properties with the `analysisKind`-specific settings.
|
||||
return { ...configWithoutScopes, ...analysisKindSpecificConfig };
|
||||
}
|
||||
|
||||
/** A subset of the `UserConfig` schema that is used by Default Setup. */
|
||||
const DEFAULT_SETUP_CONFIG_SCHEMA = {
|
||||
"threat-models": json.optional(json.array(json.string)),
|
||||
|
||||
@@ -137,6 +137,8 @@ export enum Feature {
|
||||
QaTelemetryEnabled = "qa_telemetry_enabled",
|
||||
/** Routes (some) API requests through the registry proxy. */
|
||||
ProxyApiRequests = "proxy_api_requests",
|
||||
/** Allows sections specific to analysis kinds in configuration files. */
|
||||
ScopedConfigurations = "scoped_configurations",
|
||||
/** Note that this currently only disables baseline file coverage information. */
|
||||
SkipFileCoverageOnPrs = "skip_file_coverage_on_prs",
|
||||
StartProxyUseFeaturesRelease = "start_proxy_use_features_release",
|
||||
@@ -385,6 +387,11 @@ export const featureConfig = {
|
||||
envVar: "CODEQL_ACTION_PROXY_API_REQUESTS",
|
||||
minimumVersion: undefined,
|
||||
},
|
||||
[Feature.ScopedConfigurations]: {
|
||||
defaultValue: false,
|
||||
envVar: "CODEQL_ACTION_SCOPED_CONFIGURATIONS",
|
||||
minimumVersion: undefined,
|
||||
},
|
||||
[Feature.SkipFileCoverageOnPrs]: {
|
||||
defaultValue: false,
|
||||
envVar: "CODEQL_ACTION_SKIP_FILE_COVERAGE_ON_PRS",
|
||||
|
||||
Reference in New Issue
Block a user