diff --git a/lib/entry-points.js b/lib/entry-points.js index 60bc56d157..d3c07ccf24 100644 --- a/lib/entry-points.js +++ b/lib/entry-points.js @@ -141294,7 +141294,7 @@ var import_perf_hooks4 = require("perf_hooks"); var core16 = __toESM(require_core()); // src/action-common.ts -var core10 = __toESM(require_core()); +var core8 = __toESM(require_core()); // src/actions-util.ts var fs2 = __toESM(require("fs")); @@ -145321,7 +145321,7 @@ function formatDuration(durationMs) { // src/status-report.ts var os3 = __toESM(require("os")); -var core9 = __toESM(require_core()); +var core7 = __toESM(require_core()); // src/api-client.ts var core5 = __toESM(require_core()); @@ -145608,24 +145608,22 @@ function wrapApiConfigurationError(e) { return e; } -// src/config-utils.ts -var fs9 = __toESM(require("fs")); -var path10 = __toESM(require("path")); -var import_perf_hooks = require("perf_hooks"); -var core8 = __toESM(require_core()); - -// src/feature-flags.ts -var fs5 = __toESM(require("fs")); -var path5 = __toESM(require("path")); -var semver4 = __toESM(require_semver2()); - -// src/defaults.json -var bundleVersion = "codeql-bundle-v2.25.6"; -var cliVersion = "2.25.6"; - -// src/overlay/index.ts -var fs4 = __toESM(require("fs")); -var path4 = __toESM(require("path")); +// src/config/pack-registries.ts +function parseRegistries(registriesInput) { + try { + return registriesInput ? load(registriesInput) : void 0; + } catch { + throw new ConfigurationError( + "Invalid registries input. Must be a YAML string." + ); + } +} +function parseRegistriesWithoutCredentials(registriesInput) { + return parseRegistries(registriesInput)?.map((r) => { + const { url: url2, packages, kind } = r; + return { url: url2, packages, kind }; + }); +} // src/git-utils.ts var fs3 = __toESM(require("fs")); @@ -145897,232 +145895,551 @@ async function getGeneratedFiles(workingDirectory) { return generatedFiles; } -// src/overlay/index.ts -var CODEQL_OVERLAY_MINIMUM_VERSION = "2.23.8"; -var CODEQL_OVERLAY_MINIMUM_VERSION_CPP = "2.25.0"; -var CODEQL_OVERLAY_MINIMUM_VERSION_CSHARP = "2.24.1"; -var CODEQL_OVERLAY_MINIMUM_VERSION_GO = "2.24.2"; -var CODEQL_OVERLAY_MINIMUM_VERSION_JAVA = "2.23.8"; -var CODEQL_OVERLAY_MINIMUM_VERSION_JAVASCRIPT = "2.23.9"; -var CODEQL_OVERLAY_MINIMUM_VERSION_PYTHON = "2.23.9"; -var CODEQL_OVERLAY_MINIMUM_VERSION_RUBY = "2.23.9"; -async function writeBaseDatabaseOidsFile(config, sourceRoot) { - const gitFileOids = await getFileOidsUnderPath(sourceRoot); - const gitFileOidsJson = JSON.stringify(gitFileOids); - const baseDatabaseOidsFilePath = getBaseDatabaseOidsFilePath(config); - await fs4.promises.writeFile(baseDatabaseOidsFilePath, gitFileOidsJson); +// src/status-report.ts +function getDisplayActionName(actionName) { + if (actionName === "finish" /* Analyze */) { + return "analyze"; + } + return actionName; } -async function readBaseDatabaseOidsFile(config, logger) { - const baseDatabaseOidsFilePath = getBaseDatabaseOidsFilePath(config); - try { - const contents = await fs4.promises.readFile( - baseDatabaseOidsFilePath, - "utf-8" - ); - return JSON.parse(contents); - } catch (e) { - logger.error( - `Failed to read overlay-base file OIDs from ${baseDatabaseOidsFilePath}: ${e.message || e}` - ); - throw e; +function isFirstPartyAnalysis(actionName) { + if (actionName !== "upload-sarif" /* UploadSarif */) { + return true; } + return process.env["CODEQL_ACTION_INIT_HAS_RUN" /* INIT_ACTION_HAS_RUN */] === "true"; } -async function writeOverlayChangesFile(config, sourceRoot, logger) { - const baseFileOids = await readBaseDatabaseOidsFile(config, logger); - const overlayFileOids = await getFileOidsUnderPath(sourceRoot); - const oidChangedFiles = computeChangedFiles(baseFileOids, overlayFileOids); - logger.info( - `Found ${oidChangedFiles.length} changed file(s) under ${sourceRoot} from OID comparison.` - ); - const diffRangeFiles = await getDiffRangeFilePaths(sourceRoot, logger); - const changedFiles = [.../* @__PURE__ */ new Set([...oidChangedFiles, ...diffRangeFiles])]; - const changedFilesJson = JSON.stringify({ changes: changedFiles }); - const overlayChangesFile = path4.join( - getTemporaryDirectory(), - "overlay-changes.json" - ); - logger.debug( - `Writing overlay changed files to ${overlayChangesFile}: ${changedFilesJson}` - ); - await fs4.promises.writeFile(overlayChangesFile, changedFilesJson); - return overlayChangesFile; +function isThirdPartyAnalysis(actionName) { + return !isFirstPartyAnalysis(actionName); } -function computeChangedFiles(baseFileOids, overlayFileOids) { - const changes = []; - for (const [file, oid] of Object.entries(overlayFileOids)) { - if (!(file in baseFileOids) || baseFileOids[file] !== oid) { - changes.push(file); - } +var JobStatus = /* @__PURE__ */ ((JobStatus2) => { + JobStatus2["UnknownStatus"] = "JOB_STATUS_UNKNOWN"; + JobStatus2["SuccessStatus"] = "JOB_STATUS_SUCCESS"; + JobStatus2["FailureStatus"] = "JOB_STATUS_FAILURE"; + JobStatus2["ConfigErrorStatus"] = "JOB_STATUS_CONFIGURATION_ERROR"; + return JobStatus2; +})(JobStatus || {}); +function getActionsStatus(error3, otherFailureCause) { + if (error3 || otherFailureCause) { + return error3 instanceof ConfigurationError ? "user-error" : "failure"; + } else { + return "success"; } - for (const file of Object.keys(baseFileOids)) { - if (!(file in overlayFileOids)) { - changes.push(file); - } +} +function getJobStatusDisplayName(status) { + switch (status) { + case "JOB_STATUS_SUCCESS" /* SuccessStatus */: + return "success"; + case "JOB_STATUS_FAILURE" /* FailureStatus */: + return "failure"; + case "JOB_STATUS_CONFIGURATION_ERROR" /* ConfigErrorStatus */: + return "configuration error"; + case "JOB_STATUS_UNKNOWN" /* UnknownStatus */: + return "unknown"; + default: + assertNever(status); } - return changes; } -async function getDiffRangeFilePaths(sourceRoot, logger) { - const jsonFilePath = getDiffRangesJsonFilePath(); - if (!fs4.existsSync(jsonFilePath)) { - logger.debug( - `No diff ranges JSON file found at ${jsonFilePath}; skipping.` +function setJobStatusIfUnsuccessful(actionStatus) { + if (actionStatus === "user-error") { + core7.exportVariable( + "CODEQL_ACTION_JOB_STATUS" /* JOB_STATUS */, + process.env["CODEQL_ACTION_JOB_STATUS" /* JOB_STATUS */] ?? "JOB_STATUS_CONFIGURATION_ERROR" /* ConfigErrorStatus */ ); - return []; - } - let contents; - try { - contents = await fs4.promises.readFile(jsonFilePath, "utf8"); - } catch (e) { - logger.warning( - `Failed to read diff ranges JSON file at ${jsonFilePath}: ${e}` + } else if (actionStatus === "failure" || actionStatus === "aborted") { + core7.exportVariable( + "CODEQL_ACTION_JOB_STATUS" /* JOB_STATUS */, + process.env["CODEQL_ACTION_JOB_STATUS" /* JOB_STATUS */] ?? "JOB_STATUS_FAILURE" /* FailureStatus */ ); - return []; } - let diffRanges; +} +async function createStatusReportBase(actionName, status, actionStartedAt, config, diskInfo, logger, cause, exception) { try { - diffRanges = JSON.parse(contents); - } catch (e) { - logger.warning( - `Failed to parse diff ranges JSON file at ${jsonFilePath}: ${e}` - ); - return []; - } - logger.debug( - `Read ${diffRanges.length} diff range(s) from ${jsonFilePath} for overlay changes.` - ); - const repoRoot = await getGitRoot(sourceRoot); - if (repoRoot === void 0) { - if (getOptionalInput("source-root")) { - throw new Error( - "Cannot determine git root to convert diff range paths relative to source-root. Failing to avoid omitting files from the analysis." + const commitOid = getOptionalInput("sha") || process.env["GITHUB_SHA"] || ""; + const ref = await getRef(); + const jobRunUUID = process.env["JOB_RUN_UUID" /* JOB_RUN_UUID */] || ""; + const workflowRunID = getWorkflowRunID(); + const workflowRunAttempt = getWorkflowRunAttempt(); + const workflowName = process.env["GITHUB_WORKFLOW"] || ""; + const jobName = process.env["GITHUB_JOB"] || ""; + const analysis_key = await getAnalysisKey(); + let workflowStartedAt = process.env["CODEQL_WORKFLOW_STARTED_AT" /* WORKFLOW_STARTED_AT */]; + if (workflowStartedAt === void 0) { + workflowStartedAt = actionStartedAt.toISOString(); + core7.exportVariable("CODEQL_WORKFLOW_STARTED_AT" /* WORKFLOW_STARTED_AT */, workflowStartedAt); + } + const runnerOs = getRequiredEnvParam("RUNNER_OS"); + const codeQlCliVersion = getCachedCodeQlVersion(); + const actionRef = process.env["GITHUB_ACTION_REF"] || ""; + const testingEnvironment = getTestingEnvironment(); + if (testingEnvironment) { + core7.exportVariable("CODEQL_ACTION_TESTING_ENVIRONMENT" /* TESTING_ENVIRONMENT */, testingEnvironment); + } + const isSteadyStateDefaultSetupRun = process.env["CODE_SCANNING_IS_STEADY_STATE_DEFAULT_SETUP"] === "true"; + const statusReport = { + action_name: actionName, + action_oid: "unknown", + // TODO decide if it's possible to fill this in + action_ref: actionRef, + action_started_at: actionStartedAt.toISOString(), + action_version: getActionVersion(), + analysis_kinds: config?.analysisKinds?.join(","), + analysis_key, + build_mode: config?.buildMode, + commit_oid: commitOid, + first_party_analysis: isFirstPartyAnalysis(actionName), + job_name: jobName, + job_run_uuid: jobRunUUID, + ref, + runner_os: runnerOs, + started_at: workflowStartedAt, + status, + steady_state_default_setup: isSteadyStateDefaultSetupRun, + testing_environment: testingEnvironment || "", + workflow_name: workflowName, + workflow_run_attempt: workflowRunAttempt, + workflow_run_id: workflowRunID + }; + try { + statusReport.actions_event_name = getWorkflowEventName(); + } catch (e) { + logger.warning( + `Could not determine the workflow event name: ${getErrorMessage(e)}.` ); } + if (config) { + statusReport.languages = config.languages?.join(","); + } + if (diskInfo) { + statusReport.runner_available_disk_space_bytes = diskInfo.numAvailableBytes; + statusReport.runner_total_disk_space_bytes = diskInfo.numTotalBytes; + } + if (cause) { + statusReport.cause = cause; + } + if (exception) { + statusReport.exception = exception; + } + if (status === "success" || status === "failure" || status === "aborted" || status === "user-error") { + statusReport.completed_at = (/* @__PURE__ */ new Date()).toISOString(); + } + const matrix = getRequiredInput("matrix"); + if (matrix) { + statusReport.matrix_vars = matrix; + } + if ("RUNNER_ARCH" in process.env) { + statusReport.runner_arch = process.env["RUNNER_ARCH"]; + } + if (!(runnerOs === "Linux" && isSelfHostedRunner())) { + statusReport.runner_os_release = os3.release(); + } + if (codeQlCliVersion !== void 0) { + statusReport.codeql_version = codeQlCliVersion.version; + } + const imageVersion = process.env["ImageVersion"]; + if (imageVersion) { + statusReport.runner_image_version = imageVersion; + } + return statusReport; + } catch (e) { logger.warning( - "Cannot determine git root; returning diff range paths as-is." + `Failed to gather information for telemetry: ${getErrorMessage(e)}. Will skip sending status report.` ); - return [...new Set(diffRanges.map((r) => r.path))]; + if (isInTestMode()) { + throw e; + } + return void 0; } - const relativePaths = diffRanges.map( - (r) => path4.relative(sourceRoot, path4.join(repoRoot, r.path)).replaceAll(path4.sep, "/") - ).filter((rel) => !rel.startsWith("..")); - return [...new Set(relativePaths)]; } - -// src/tools-features.ts -var semver3 = __toESM(require_semver2()); -function isSupportedToolsFeature(versionInfo, feature) { - return !!versionInfo.features && versionInfo.features[feature]; +var OUT_OF_DATE_MSG = "CodeQL Action is out-of-date. Please upgrade to the latest version of `codeql-action`."; +var INCOMPATIBLE_MSG = "CodeQL Action version is incompatible with the API endpoint. Please update to a compatible version of `codeql-action`."; +async function sendStatusReport(statusReport) { + setJobStatusIfUnsuccessful(statusReport.status); + const statusReportJSON = JSON.stringify(statusReport); + core7.debug(`Sending status report: ${statusReportJSON}`); + if (isInTestMode()) { + core7.debug("In test mode. Status reports are not uploaded."); + return; + } + const nwo = getRepositoryNwo(); + const client = getApiClient(); + try { + await client.request( + "PUT /repos/:owner/:repo/code-scanning/analysis/status", + { + owner: nwo.owner, + repo: nwo.repo, + data: statusReportJSON + } + ); + } catch (e) { + const httpError = asHTTPError(e); + if (httpError !== void 0) { + switch (httpError.status) { + case 403: + if (getWorkflowEventName() === "push" && process.env["GITHUB_ACTOR"] === "dependabot[bot]") { + core7.warning( + `Workflows triggered by Dependabot on the "push" event run with read-only access. Uploading CodeQL results requires write access. To use CodeQL with Dependabot, please ensure you are using the "pull_request" event for this workflow and avoid triggering on the "push" event for Dependabot branches. See ${"https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/customizing-your-advanced-setup-for-code-scanning#scanning-on-push" /* SCANNING_ON_PUSH */} for more information on how to configure these events.` + ); + } else { + core7.warning( + `This run of the CodeQL Action does not have permission to access the CodeQL Action API endpoints. This could be because the Action is running on a pull request from a fork. If not, please ensure the workflow has at least the 'security-events: read' permission. Details: ${httpError.message}` + ); + } + return; + case 404: + core7.warning(httpError.message); + return; + case 422: + if (getRequiredEnvParam("GITHUB_SERVER_URL") !== GITHUB_DOTCOM_URL) { + core7.debug(INCOMPATIBLE_MSG); + } else { + core7.debug(OUT_OF_DATE_MSG); + } + return; + } + } + core7.warning( + `An unexpected error occurred when sending a status report: ${getErrorMessage( + e + )}` + ); + } } -var SafeArtifactUploadVersion = "2.20.3"; -function isSafeArtifactUpload(codeQlVersion) { - return !codeQlVersion ? true : semver3.gte(codeQlVersion, SafeArtifactUploadVersion); +async function createInitWithConfigStatusReport(config, initStatusReport, configFile, totalCacheSize, overlayBaseDatabaseStats, dependencyCachingResults) { + const languages = config.languages.join(","); + const paths = (config.originalUserInput.paths || []).join(","); + const pathsIgnore = (config.originalUserInput["paths-ignore"] || []).join( + "," + ); + const disableDefaultQueries = config.originalUserInput["disable-default-queries"] ? languages : ""; + const queries = []; + let queriesInput = getOptionalInput("queries")?.trim(); + if (queriesInput === void 0 || queriesInput.startsWith("+")) { + queries.push( + ...(config.originalUserInput.queries || []).map((q) => q.uses) + ); + } + if (queriesInput !== void 0) { + queriesInput = queriesInput.startsWith("+") ? queriesInput.slice(1) : queriesInput; + queries.push(...queriesInput.split(",")); + } + let packs = {}; + if (Array.isArray(config.computedConfig.packs)) { + packs[config.languages[0]] = config.computedConfig.packs; + } else if (config.computedConfig.packs !== void 0) { + packs = config.computedConfig.packs; + } + return { + ...initStatusReport, + config_file: configFile ?? "", + disable_default_queries: disableDefaultQueries, + paths, + paths_ignore: pathsIgnore, + queries: queries.join(","), + packs: JSON.stringify(packs), + trap_cache_languages: Object.keys(config.trapCaches).join(","), + trap_cache_download_size_bytes: totalCacheSize, + trap_cache_download_duration_ms: Math.round(config.trapCacheDownloadTime), + overlay_base_database_download_size_bytes: overlayBaseDatabaseStats?.databaseSizeBytes, + overlay_base_database_download_duration_ms: overlayBaseDatabaseStats?.databaseDownloadDurationMs, + dependency_caching_restore_results: dependencyCachingResults, + query_filters: JSON.stringify( + config.originalUserInput["query-filters"] ?? [] + ), + registries: JSON.stringify( + parseRegistriesWithoutCredentials(getOptionalInput("registries")) ?? [] + ) + }; +} +async function sendUnhandledErrorStatusReport(actionName, actionStartedAt, error3, logger) { + try { + const statusReport = await createStatusReportBase( + actionName, + "failure", + actionStartedAt, + void 0, + void 0, + logger, + `Unhandled CodeQL Action error: ${getErrorMessage(error3)}`, + error3 instanceof Error ? error3.stack : void 0 + ); + if (statusReport !== void 0) { + await sendStatusReport(statusReport); + } + } catch (e) { + logger.warning( + `Failed to send the unhandled error status report: ${getErrorMessage(e)}.` + ); + if (isInTestMode()) { + throw e; + } + } +} + +// src/action-common.ts +async function runInActions(action) { + const startedAt = /* @__PURE__ */ new Date(); + const logger = getActionsLogger(); + const env = getEnv(); + const actionsEnv = getActionsEnv(); + try { + await action.run({ + name: action.name, + startedAt, + logger, + env, + actions: actionsEnv + }); + } catch (error3) { + core8.setFailed( + `${getDisplayActionName(action.name)} action failed: ${getErrorMessage(error3)}` + ); + await sendUnhandledErrorStatusReport(action.name, startedAt, error3, logger); + } } // src/feature-flags.ts -var DEFAULT_VERSION_FEATURE_FLAG_PREFIX = "default_codeql_version_"; -var DEFAULT_VERSION_FEATURE_FLAG_SUFFIX = "_enabled"; -var CODEQL_VERSION_ZSTD_BUNDLE = "2.19.0"; -var LINKED_CODEQL_VERSION = { - cliVersion, - tagName: bundleVersion -}; -var featureConfig = { - ["allow_multiple_analysis_kinds" /* AllowMultipleAnalysisKinds */]: { - defaultValue: false, - envVar: "CODEQL_ACTION_ALLOW_MULTIPLE_ANALYSIS_KINDS", - minimumVersion: void 0 - }, - ["allow_toolcache_input" /* AllowToolcacheInput */]: { - defaultValue: false, - envVar: "CODEQL_ACTION_ALLOW_TOOLCACHE_INPUT", - minimumVersion: void 0 - }, - ["cleanup_trap_caches" /* CleanupTrapCaches */]: { - defaultValue: false, - envVar: "CODEQL_ACTION_CLEANUP_TRAP_CACHES", - minimumVersion: void 0 - }, - ["config_file_repository_property" /* ConfigFileRepositoryProperty */]: { - defaultValue: false, - envVar: "CODEQL_ACTION_CONFIG_FILE_REPOSITORY_PROPERTY", - minimumVersion: void 0 - }, - ["cpp_dependency_installation_enabled" /* CppDependencyInstallation */]: { - defaultValue: false, - envVar: "CODEQL_EXTRACTOR_CPP_AUTOINSTALL_DEPENDENCIES", - legacyApi: true, - minimumVersion: "2.15.0" - }, - ["csharp_cache_bmn" /* CsharpCacheBuildModeNone */]: { - defaultValue: false, - envVar: "CODEQL_ACTION_CSHARP_CACHE_BMN", - minimumVersion: void 0 - }, - ["csharp_new_cache_key" /* CsharpNewCacheKey */]: { - defaultValue: false, - envVar: "CODEQL_ACTION_CSHARP_NEW_CACHE_KEY", - minimumVersion: void 0 - }, - ["diff_informed_queries" /* DiffInformedQueries */]: { - defaultValue: true, - envVar: "CODEQL_ACTION_DIFF_INFORMED_QUERIES", - minimumVersion: "2.21.0" - }, - ["disable_csharp_buildless" /* DisableCsharpBuildless */]: { - defaultValue: false, - envVar: "CODEQL_ACTION_DISABLE_CSHARP_BUILDLESS", - minimumVersion: void 0 - }, - ["disable_java_buildless_enabled" /* DisableJavaBuildlessEnabled */]: { - defaultValue: false, - envVar: "CODEQL_ACTION_DISABLE_JAVA_BUILDLESS", - legacyApi: true, - minimumVersion: void 0 - }, - ["disable_kotlin_analysis_enabled" /* DisableKotlinAnalysisEnabled */]: { - defaultValue: false, - envVar: "CODEQL_DISABLE_KOTLIN_ANALYSIS", - legacyApi: true, - minimumVersion: void 0 - }, - ["export_diagnostics_enabled" /* ExportDiagnosticsEnabled */]: { - defaultValue: true, - envVar: "CODEQL_ACTION_EXPORT_DIAGNOSTICS", - legacyApi: true, - minimumVersion: void 0 - }, - ["force_jgit" /* ForceJGit */]: { - defaultValue: false, - envVar: "CODEQL_ACTION_FORCE_JGIT", - minimumVersion: void 0 - }, - ["force_nightly" /* ForceNightly */]: { - defaultValue: false, - envVar: "CODEQL_ACTION_FORCE_NIGHTLY", - minimumVersion: void 0 - }, - ["ignore_generated_files" /* IgnoreGeneratedFiles */]: { - defaultValue: false, - envVar: "CODEQL_ACTION_IGNORE_GENERATED_FILES", - minimumVersion: void 0 - }, - ["java_network_debugging" /* JavaNetworkDebugging */]: { - defaultValue: false, - envVar: "CODEQL_ACTION_JAVA_NETWORK_DEBUGGING", - minimumVersion: void 0 - }, - ["overlay_analysis" /* OverlayAnalysis */]: { - defaultValue: false, - envVar: "CODEQL_ACTION_OVERLAY_ANALYSIS", - minimumVersion: CODEQL_OVERLAY_MINIMUM_VERSION - }, - // Per-language overlay feature flags. Each has minimumVersion set to the - // minimum CLI version that supports overlay analysis for that language. - // Only languages that are GA or in staff-ship should have feature flags here. - ["overlay_analysis_code_scanning_cpp" /* OverlayAnalysisCodeScanningCpp */]: { - defaultValue: false, - envVar: "CODEQL_ACTION_OVERLAY_ANALYSIS_CODE_SCANNING_CPP", - minimumVersion: CODEQL_OVERLAY_MINIMUM_VERSION_CPP +var fs5 = __toESM(require("fs")); +var path5 = __toESM(require("path")); +var semver4 = __toESM(require_semver2()); + +// src/defaults.json +var bundleVersion = "codeql-bundle-v2.25.6"; +var cliVersion = "2.25.6"; + +// src/overlay/index.ts +var fs4 = __toESM(require("fs")); +var path4 = __toESM(require("path")); +var CODEQL_OVERLAY_MINIMUM_VERSION = "2.23.8"; +var CODEQL_OVERLAY_MINIMUM_VERSION_CPP = "2.25.0"; +var CODEQL_OVERLAY_MINIMUM_VERSION_CSHARP = "2.24.1"; +var CODEQL_OVERLAY_MINIMUM_VERSION_GO = "2.24.2"; +var CODEQL_OVERLAY_MINIMUM_VERSION_JAVA = "2.23.8"; +var CODEQL_OVERLAY_MINIMUM_VERSION_JAVASCRIPT = "2.23.9"; +var CODEQL_OVERLAY_MINIMUM_VERSION_PYTHON = "2.23.9"; +var CODEQL_OVERLAY_MINIMUM_VERSION_RUBY = "2.23.9"; +async function writeBaseDatabaseOidsFile(config, sourceRoot) { + const gitFileOids = await getFileOidsUnderPath(sourceRoot); + const gitFileOidsJson = JSON.stringify(gitFileOids); + const baseDatabaseOidsFilePath = getBaseDatabaseOidsFilePath(config); + await fs4.promises.writeFile(baseDatabaseOidsFilePath, gitFileOidsJson); +} +async function readBaseDatabaseOidsFile(config, logger) { + const baseDatabaseOidsFilePath = getBaseDatabaseOidsFilePath(config); + try { + const contents = await fs4.promises.readFile( + baseDatabaseOidsFilePath, + "utf-8" + ); + return JSON.parse(contents); + } catch (e) { + logger.error( + `Failed to read overlay-base file OIDs from ${baseDatabaseOidsFilePath}: ${e.message || e}` + ); + throw e; + } +} +async function writeOverlayChangesFile(config, sourceRoot, logger) { + const baseFileOids = await readBaseDatabaseOidsFile(config, logger); + const overlayFileOids = await getFileOidsUnderPath(sourceRoot); + const oidChangedFiles = computeChangedFiles(baseFileOids, overlayFileOids); + logger.info( + `Found ${oidChangedFiles.length} changed file(s) under ${sourceRoot} from OID comparison.` + ); + const diffRangeFiles = await getDiffRangeFilePaths(sourceRoot, logger); + const changedFiles = [.../* @__PURE__ */ new Set([...oidChangedFiles, ...diffRangeFiles])]; + const changedFilesJson = JSON.stringify({ changes: changedFiles }); + const overlayChangesFile = path4.join( + getTemporaryDirectory(), + "overlay-changes.json" + ); + logger.debug( + `Writing overlay changed files to ${overlayChangesFile}: ${changedFilesJson}` + ); + await fs4.promises.writeFile(overlayChangesFile, changedFilesJson); + return overlayChangesFile; +} +function computeChangedFiles(baseFileOids, overlayFileOids) { + const changes = []; + for (const [file, oid] of Object.entries(overlayFileOids)) { + if (!(file in baseFileOids) || baseFileOids[file] !== oid) { + changes.push(file); + } + } + for (const file of Object.keys(baseFileOids)) { + if (!(file in overlayFileOids)) { + changes.push(file); + } + } + return changes; +} +async function getDiffRangeFilePaths(sourceRoot, logger) { + const jsonFilePath = getDiffRangesJsonFilePath(); + if (!fs4.existsSync(jsonFilePath)) { + logger.debug( + `No diff ranges JSON file found at ${jsonFilePath}; skipping.` + ); + return []; + } + let contents; + try { + contents = await fs4.promises.readFile(jsonFilePath, "utf8"); + } catch (e) { + logger.warning( + `Failed to read diff ranges JSON file at ${jsonFilePath}: ${e}` + ); + return []; + } + let diffRanges; + try { + diffRanges = JSON.parse(contents); + } catch (e) { + logger.warning( + `Failed to parse diff ranges JSON file at ${jsonFilePath}: ${e}` + ); + return []; + } + logger.debug( + `Read ${diffRanges.length} diff range(s) from ${jsonFilePath} for overlay changes.` + ); + const repoRoot = await getGitRoot(sourceRoot); + if (repoRoot === void 0) { + if (getOptionalInput("source-root")) { + throw new Error( + "Cannot determine git root to convert diff range paths relative to source-root. Failing to avoid omitting files from the analysis." + ); + } + logger.warning( + "Cannot determine git root; returning diff range paths as-is." + ); + return [...new Set(diffRanges.map((r) => r.path))]; + } + const relativePaths = diffRanges.map( + (r) => path4.relative(sourceRoot, path4.join(repoRoot, r.path)).replaceAll(path4.sep, "/") + ).filter((rel) => !rel.startsWith("..")); + return [...new Set(relativePaths)]; +} + +// src/tools-features.ts +var semver3 = __toESM(require_semver2()); +function isSupportedToolsFeature(versionInfo, feature) { + return !!versionInfo.features && versionInfo.features[feature]; +} +var SafeArtifactUploadVersion = "2.20.3"; +function isSafeArtifactUpload(codeQlVersion) { + return !codeQlVersion ? true : semver3.gte(codeQlVersion, SafeArtifactUploadVersion); +} + +// src/feature-flags.ts +var DEFAULT_VERSION_FEATURE_FLAG_PREFIX = "default_codeql_version_"; +var DEFAULT_VERSION_FEATURE_FLAG_SUFFIX = "_enabled"; +var CODEQL_VERSION_ZSTD_BUNDLE = "2.19.0"; +var LINKED_CODEQL_VERSION = { + cliVersion, + tagName: bundleVersion +}; +var featureConfig = { + ["allow_multiple_analysis_kinds" /* AllowMultipleAnalysisKinds */]: { + defaultValue: false, + envVar: "CODEQL_ACTION_ALLOW_MULTIPLE_ANALYSIS_KINDS", + minimumVersion: void 0 + }, + ["allow_toolcache_input" /* AllowToolcacheInput */]: { + defaultValue: false, + envVar: "CODEQL_ACTION_ALLOW_TOOLCACHE_INPUT", + minimumVersion: void 0 + }, + ["cleanup_trap_caches" /* CleanupTrapCaches */]: { + defaultValue: false, + envVar: "CODEQL_ACTION_CLEANUP_TRAP_CACHES", + minimumVersion: void 0 + }, + ["config_file_repository_property" /* ConfigFileRepositoryProperty */]: { + defaultValue: false, + envVar: "CODEQL_ACTION_CONFIG_FILE_REPOSITORY_PROPERTY", + minimumVersion: void 0 + }, + ["cpp_dependency_installation_enabled" /* CppDependencyInstallation */]: { + defaultValue: false, + envVar: "CODEQL_EXTRACTOR_CPP_AUTOINSTALL_DEPENDENCIES", + legacyApi: true, + minimumVersion: "2.15.0" + }, + ["csharp_cache_bmn" /* CsharpCacheBuildModeNone */]: { + defaultValue: false, + envVar: "CODEQL_ACTION_CSHARP_CACHE_BMN", + minimumVersion: void 0 + }, + ["csharp_new_cache_key" /* CsharpNewCacheKey */]: { + defaultValue: false, + envVar: "CODEQL_ACTION_CSHARP_NEW_CACHE_KEY", + minimumVersion: void 0 + }, + ["diff_informed_queries" /* DiffInformedQueries */]: { + defaultValue: true, + envVar: "CODEQL_ACTION_DIFF_INFORMED_QUERIES", + minimumVersion: "2.21.0" + }, + ["disable_csharp_buildless" /* DisableCsharpBuildless */]: { + defaultValue: false, + envVar: "CODEQL_ACTION_DISABLE_CSHARP_BUILDLESS", + minimumVersion: void 0 + }, + ["disable_java_buildless_enabled" /* DisableJavaBuildlessEnabled */]: { + defaultValue: false, + envVar: "CODEQL_ACTION_DISABLE_JAVA_BUILDLESS", + legacyApi: true, + minimumVersion: void 0 + }, + ["disable_kotlin_analysis_enabled" /* DisableKotlinAnalysisEnabled */]: { + defaultValue: false, + envVar: "CODEQL_DISABLE_KOTLIN_ANALYSIS", + legacyApi: true, + minimumVersion: void 0 + }, + ["export_diagnostics_enabled" /* ExportDiagnosticsEnabled */]: { + defaultValue: true, + envVar: "CODEQL_ACTION_EXPORT_DIAGNOSTICS", + legacyApi: true, + minimumVersion: void 0 + }, + ["force_jgit" /* ForceJGit */]: { + defaultValue: false, + envVar: "CODEQL_ACTION_FORCE_JGIT", + minimumVersion: void 0 + }, + ["force_nightly" /* ForceNightly */]: { + defaultValue: false, + envVar: "CODEQL_ACTION_FORCE_NIGHTLY", + minimumVersion: void 0 + }, + ["ignore_generated_files" /* IgnoreGeneratedFiles */]: { + defaultValue: false, + envVar: "CODEQL_ACTION_IGNORE_GENERATED_FILES", + minimumVersion: void 0 + }, + ["java_network_debugging" /* JavaNetworkDebugging */]: { + defaultValue: false, + envVar: "CODEQL_ACTION_JAVA_NETWORK_DEBUGGING", + minimumVersion: void 0 + }, + ["new_remote_file_addresses" /* NewRemoteFileAddresses */]: { + defaultValue: false, + envVar: "CODEQL_ACTION_NEW_REMOTE_FILE_ADDRESSES", + minimumVersion: void 0 + }, + ["overlay_analysis" /* OverlayAnalysis */]: { + defaultValue: false, + envVar: "CODEQL_ACTION_OVERLAY_ANALYSIS", + minimumVersion: CODEQL_OVERLAY_MINIMUM_VERSION + }, + // Per-language overlay feature flags. Each has minimumVersion set to the + // minimum CLI version that supports overlay analysis for that language. + // Only languages that are GA or in staff-ship should have feature flags here. + ["overlay_analysis_code_scanning_cpp" /* OverlayAnalysisCodeScanningCpp */]: { + defaultValue: false, + envVar: "CODEQL_ACTION_OVERLAY_ANALYSIS_CODE_SCANNING_CPP", + minimumVersion: CODEQL_OVERLAY_MINIMUM_VERSION_CPP }, ["overlay_analysis_code_scanning_csharp" /* OverlayAnalysisCodeScanningCsharp */]: { defaultValue: false, @@ -146254,3114 +146571,2896 @@ var featureConfig = { minimumVersion: void 0 } }; -var FEATURE_FLAGS_FILE_NAME = "cached-feature-flags.json"; -var OfflineFeatures = class { - constructor(logger) { - this.logger = logger; - } - logger; - async getEnabledDefaultCliVersions(_variant) { - return { - enabledVersions: [LINKED_CODEQL_VERSION] - }; - } - /** - * Gets the `FeatureConfig` for `feature`. - */ - getFeatureConfig(feature) { - return featureConfig[feature]; - } - /** - * Determines whether `feature` is enabled without consulting the GitHub API. - * - * @param feature The feature to check. - * @param codeql An optional CodeQL object. If provided, and a `minimumVersion` is specified for the - * feature, the version of the CodeQL CLI will be checked against the minimum version. - * If the version is less than the minimum version, the feature will be considered - * disabled. If not provided, and a `minimumVersion` is specified for the feature, then - * this function will throw. - * @returns true if the feature is enabled, false otherwise. - * - * @throws if a `minimumVersion` is specified for the feature, and `codeql` is not provided. - */ - async getValue(feature, codeql) { - const offlineValue = await this.getOfflineValue(feature, codeql); - if (offlineValue !== void 0) { - return offlineValue; - } - return this.getDefaultValue(feature); - } - /** - * Determines whether `feature` is enabled using the CLI and environment variables. - */ - async getOfflineValue(feature, codeql) { - const config = this.getFeatureConfig(feature); - if (!codeql && config.minimumVersion) { - throw new Error( - `Internal error: A minimum version is specified for feature ${feature}, but no instance of CodeQL was provided.` - ); - } - if (!codeql && config.toolsFeature) { - throw new Error( - `Internal error: A required tools feature is specified for feature ${feature}, but no instance of CodeQL was provided.` - ); - } - const envVar = (process.env[config.envVar] || "").toLocaleLowerCase(); - if (envVar === "false") { - this.logger.debug( - `Feature ${feature} is disabled via the environment variable ${config.envVar}.` - ); - return false; - } - const minimumVersion2 = config.minimumVersion; - if (codeql && minimumVersion2) { - if (!await codeQlVersionAtLeast(codeql, minimumVersion2)) { - this.logger.debug( - `Feature ${feature} is disabled because the CodeQL CLI version is older than the minimum version ${minimumVersion2}.` - ); - return false; - } else { - this.logger.debug( - `CodeQL CLI version ${(await codeql.getVersion()).version} is newer than the minimum version ${minimumVersion2} for feature ${feature}.` - ); - } - } - const toolsFeature = config.toolsFeature; - if (codeql && toolsFeature) { - if (!await codeql.supportsFeature(toolsFeature)) { - this.logger.debug( - `Feature ${feature} is disabled because the CodeQL CLI version does not support the required tools feature ${toolsFeature}.` - ); - return false; - } else { - this.logger.debug( - `CodeQL CLI version ${(await codeql.getVersion()).version} supports the required tools feature ${toolsFeature} for feature ${feature}.` - ); - } - } - if (envVar === "true") { - this.logger.debug( - `Feature ${feature} is enabled via the environment variable ${config.envVar}.` - ); - return true; - } - return void 0; - } - /** Gets the default value of `feature`. */ - async getDefaultValue(feature) { - const config = this.getFeatureConfig(feature); - const defaultValue = config.defaultValue; - this.logger.debug( - `Feature ${feature} is ${defaultValue ? "enabled" : "disabled"} due to its default value.` - ); - return defaultValue; - } -}; -var Features = class extends OfflineFeatures { - gitHubFeatureFlags; - constructor(repositoryNwo, tempDir, logger) { - super(logger); - this.gitHubFeatureFlags = new GitHubFeatureFlags( - repositoryNwo, - path5.join(tempDir, FEATURE_FLAGS_FILE_NAME), - logger - ); - } - async getEnabledDefaultCliVersions(variant) { - if (supportsFeatureFlags(variant)) { - return await this.gitHubFeatureFlags.getEnabledDefaultCliVersionsFromFlags(); - } - return super.getEnabledDefaultCliVersions(variant); - } - /** - * - * @param feature The feature to check. - * @param codeql An optional CodeQL object. If provided, and a `minimumVersion` is specified for the - * feature, the version of the CodeQL CLI will be checked against the minimum version. - * If the version is less than the minimum version, the feature will be considered - * disabled. If not provided, and a `minimumVersion` is specified for the feature, then - * this function will throw. - * @returns true if the feature is enabled, false otherwise. - * - * @throws if a `minimumVersion` is specified for the feature, and `codeql` is not provided. - */ - async getValue(feature, codeql) { - const offlineValue = await this.getOfflineValue(feature, codeql); - if (offlineValue !== void 0) { - return offlineValue; - } - const apiValue = await this.gitHubFeatureFlags.getValue(feature); - if (apiValue !== void 0) { - this.logger.debug( - `Feature ${feature} is ${apiValue ? "enabled" : "disabled"} via the GitHub API.` - ); - return apiValue; - } - return this.getDefaultValue(feature); - } -}; -var GitHubFeatureFlags = class { - constructor(repositoryNwo, featureFlagsFile, logger) { - this.repositoryNwo = repositoryNwo; - this.featureFlagsFile = featureFlagsFile; - this.logger = logger; - this.hasAccessedRemoteFeatureFlags = false; - } - repositoryNwo; - featureFlagsFile; - logger; - cachedApiResponse; - // We cache whether the feature flags were accessed or not in order to accurately report whether flags were - // incorrectly configured vs. inaccessible in our telemetry. - hasAccessedRemoteFeatureFlags; - getCliVersionFromFeatureFlag(f) { - if (!f.startsWith(DEFAULT_VERSION_FEATURE_FLAG_PREFIX) || !f.endsWith(DEFAULT_VERSION_FEATURE_FLAG_SUFFIX)) { - return void 0; - } - const version = f.substring( - DEFAULT_VERSION_FEATURE_FLAG_PREFIX.length, - f.length - DEFAULT_VERSION_FEATURE_FLAG_SUFFIX.length - ).replace(/_/g, "."); - if (!semver4.valid(version)) { - this.logger.warning( - `Ignoring feature flag ${f} as it does not specify a valid CodeQL version.` - ); - return void 0; - } - return version; - } - /** - * Returns CLI versions enabled by `default_codeql_version_*_enabled` feature - * flags, sorted from highest to lowest. Falls back to the version pinned in - * `defaults.json` if no such flags are enabled. - */ - async getEnabledDefaultCliVersionsFromFlags() { - const response = await this.getAllFeatures(); - const sortedCliVersions = Object.entries(response).map( - ([f, isEnabled]) => isEnabled ? this.getCliVersionFromFeatureFlag(f) : void 0 - ).filter((f) => f !== void 0).sort(semver4.rcompare); - if (sortedCliVersions.length === 0) { - this.logger.warning( - `Feature flags do not specify a default CLI version. Falling back to the CLI version shipped with the Action. This is ${cliVersion}.` - ); - const result = { - enabledVersions: [LINKED_CODEQL_VERSION] - }; - if (this.hasAccessedRemoteFeatureFlags) { - result.toolsFeatureFlagsValid = false; - } - return result; - } - this.logger.debug( - `Derived default CLI version of ${sortedCliVersions[0]} from feature flags.` - ); - return { - enabledVersions: sortedCliVersions.map((cliVersion2) => ({ - cliVersion: cliVersion2, - tagName: `codeql-bundle-v${cliVersion2}` - })), - toolsFeatureFlagsValid: true - }; - } - async getValue(feature) { - const response = await this.getAllFeatures(); - if (response === void 0) { - this.logger.debug(`No feature flags API response for ${feature}.`); - return void 0; - } - const features = response[feature]; - if (features === void 0) { - this.logger.debug(`Feature '${feature}' undefined in API response.`); - return void 0; - } - return !!features; - } - async getAllFeatures() { - if (this.cachedApiResponse !== void 0) { - return this.cachedApiResponse; - } - const fileFlags = await this.readLocalFlags(); - if (fileFlags !== void 0) { - this.cachedApiResponse = fileFlags; - return fileFlags; - } - let remoteFlags = await this.loadApiResponse(); - if (remoteFlags === void 0) { - remoteFlags = {}; - } - this.cachedApiResponse = remoteFlags; - await this.writeLocalFlags(remoteFlags); - return remoteFlags; - } - async readLocalFlags() { - try { - if (fs5.existsSync(this.featureFlagsFile)) { - this.logger.debug( - `Loading feature flags from ${this.featureFlagsFile}` - ); - return JSON.parse( - fs5.readFileSync(this.featureFlagsFile, "utf8") - ); - } - } catch (e) { - this.logger.warning( - `Error reading cached feature flags file ${this.featureFlagsFile}: ${e}. Requesting from GitHub instead.` - ); - } - return void 0; - } - async writeLocalFlags(flags) { - try { - this.logger.debug(`Writing feature flags to ${this.featureFlagsFile}`); - fs5.writeFileSync(this.featureFlagsFile, JSON.stringify(flags)); - } catch (e) { - this.logger.warning( - `Error writing cached feature flags file ${this.featureFlagsFile}: ${e}.` - ); - } - } - async loadApiResponse() { - try { - const featuresToRequest = Object.entries(featureConfig).filter( - ([, config]) => !config.legacyApi - ).map(([f]) => f); - const FEATURES_PER_REQUEST = 25; - const featureChunks = []; - while (featuresToRequest.length > 0) { - featureChunks.push(featuresToRequest.splice(0, FEATURES_PER_REQUEST)); - } - let remoteFlags = {}; - for (const chunk of featureChunks) { - const response = await getApiClient().request( - "GET /repos/:owner/:repo/code-scanning/codeql-action/features", - { - owner: this.repositoryNwo.owner, - repo: this.repositoryNwo.repo, - features: chunk.join(",") - } - ); - const chunkFlags = response.data; - remoteFlags = { ...remoteFlags, ...chunkFlags }; - } - this.logger.debug( - "Loaded the following default values for the feature flags from the CodeQL Action API:" - ); - for (const [feature, value] of Object.entries(remoteFlags).sort( - ([nameA], [nameB]) => nameA.localeCompare(nameB) - )) { - this.logger.debug(` ${feature}: ${value}`); - } - this.hasAccessedRemoteFeatureFlags = true; - return remoteFlags; - } catch (e) { - const httpError = asHTTPError(e); - if (httpError?.status === 403) { - this.logger.warning( - `This run of the CodeQL Action does not have permission to access the CodeQL Action API endpoints. As a result, it will not be opted into any experimental features. This could be because the Action is running on a pull request from a fork. If not, please ensure the workflow has at least the 'security-events: read' permission. Details: ${httpError.message}` - ); - this.hasAccessedRemoteFeatureFlags = false; - return {}; - } else { - throw new Error( - `Encountered an error while trying to determine feature enablement: ${e}` - ); - } - } - } -}; -function supportsFeatureFlags(githubVariant) { - return githubVariant === "GitHub.com" /* DOTCOM */ || githubVariant === "GitHub Enterprise Cloud with data residency" /* GHEC_DR */; -} -function initFeatures(gitHubVersion, repositoryNwo, tempDir, logger) { - if (!supportsFeatureFlags(gitHubVersion.type)) { - logger.debug( - "Not running against github.com. Using default values for all features." - ); - return new OfflineFeatures(logger); - } else { - return new Features(repositoryNwo, tempDir, logger); - } -} - -// src/analyses.ts -var AnalysisKind = /* @__PURE__ */ ((AnalysisKind2) => { - AnalysisKind2["CodeScanning"] = "code-scanning"; - AnalysisKind2["CodeQuality"] = "code-quality"; - AnalysisKind2["RiskAssessment"] = "risk-assessment"; - return AnalysisKind2; -})(AnalysisKind || {}); -var compatibilityMatrix = { - ["code-scanning" /* CodeScanning */]: /* @__PURE__ */ new Set(["code-quality" /* CodeQuality */]), - ["code-quality" /* CodeQuality */]: /* @__PURE__ */ new Set(["code-scanning" /* CodeScanning */]), - ["risk-assessment" /* RiskAssessment */]: /* @__PURE__ */ new Set() -}; -var supportedAnalysisKinds = new Set(Object.values(AnalysisKind)); -async function parseAnalysisKinds(input) { - const components = input.split(","); - if (components.length < 1) { - throw new ConfigurationError( - "At least one analysis kind must be configured." - ); - } - for (const component of components) { - if (!supportedAnalysisKinds.has(component)) { - throw new ConfigurationError(`Unknown analysis kind: ${component}`); - } - } - return Array.from( - new Set(components.map((component) => component)) - ); -} -var cachedAnalysisKinds; -function isOnlyCodeScanningEnabled(analysisKinds) { - return analysisKinds.length === 1 && analysisKinds[0] === "code-scanning" /* CodeScanning */; -} -function makeAnalysisKindUsageError(message) { - return `The \`analysis-kinds\` input is experimental and for GitHub-internal use only. Its behaviour may change at any time or be removed entirely. ${message}`; -} -async function getAnalysisKinds(logger, features, skipCache = false) { - if (!skipCache && cachedAnalysisKinds !== void 0) { - return cachedAnalysisKinds; - } - const analysisKinds = await parseAnalysisKinds( - getRequiredInput("analysis-kinds") - ); - if (!isInTestMode() && !isDynamicWorkflow() && !isOnlyCodeScanningEnabled(analysisKinds)) { - const codeQualityHint = analysisKinds.includes("code-quality" /* CodeQuality */) ? " If your intention is to use quality queries outside of Code Quality, use the `queries` input with `code-quality` instead." : ""; - logger.error( - makeAnalysisKindUsageError( - `An analysis kind other than \`code-scanning\` was specified in a custom workflow. This is not supported and will become a fatal error in a future version of the CodeQL Action.${codeQualityHint}` - ) - ); - } - const qualityQueriesInput = getOptionalInput("quality-queries"); - if (qualityQueriesInput !== void 0) { - logger.warning( - "The `quality-queries` input is deprecated and will be removed in a future version of the CodeQL Action. Use the `analysis-kinds` input to configure different analysis kinds instead." - ); - } - if (!analysisKinds.includes("code-quality" /* CodeQuality */) && qualityQueriesInput !== void 0) { - analysisKinds.push("code-quality" /* CodeQuality */); - } - for (const analysisKind of analysisKinds) { - for (const otherAnalysisKind of analysisKinds) { - if (analysisKind === otherAnalysisKind) continue; - if (!compatibilityMatrix[analysisKind].has(otherAnalysisKind)) { - throw new ConfigurationError( - `${analysisKind} and ${otherAnalysisKind} cannot be enabled at the same time` - ); - } - } - } - if (!isInTestMode() && analysisKinds.length > 1 && !await features.getValue("allow_multiple_analysis_kinds" /* AllowMultipleAnalysisKinds */)) { - logger.error( - makeAnalysisKindUsageError( - "Specifying multiple values as input is no longer supported. Continuing with only `analysis-kinds: code-scanning`." - ) - ); - cachedAnalysisKinds = ["code-scanning" /* CodeScanning */]; - return cachedAnalysisKinds; - } - cachedAnalysisKinds = analysisKinds; - return cachedAnalysisKinds; -} -var codeQualityQueries = ["code-quality"]; -var CodeScanning = { - kind: "code-scanning" /* CodeScanning */, - name: "code scanning", - target: "PUT /repos/:owner/:repo/code-scanning/analysis" /* CODE_SCANNING */, - sarifExtension: ".sarif", - sarifPredicate: (name) => name.endsWith(CodeScanning.sarifExtension) && !CodeQuality.sarifPredicate(name) && !RiskAssessment.sarifPredicate(name), - fixCategory: (_2, category) => category, - sentinelPrefix: "CODEQL_UPLOAD_SARIF_", - transformPayload: (payload) => payload -}; -var CodeQuality = { - kind: "code-quality" /* CodeQuality */, - name: "code quality", - target: "PUT /repos/:owner/:repo/code-quality/analysis" /* CODE_QUALITY */, - sarifExtension: ".quality.sarif", - sarifPredicate: (name) => name.endsWith(CodeQuality.sarifExtension), - fixCategory: fixCodeQualityCategory, - sentinelPrefix: "CODEQL_UPLOAD_QUALITY_SARIF_", - transformPayload: (payload) => payload -}; -function addAssessmentId(payload) { - const rawAssessmentId = getRequiredEnvParam("CODEQL_ACTION_RISK_ASSESSMENT_ID" /* RISK_ASSESSMENT_ID */); - const assessmentId = parseInt(rawAssessmentId, 10); - if (Number.isNaN(assessmentId)) { - throw new Error( - `${"CODEQL_ACTION_RISK_ASSESSMENT_ID" /* RISK_ASSESSMENT_ID */} must not be NaN: ${rawAssessmentId}` - ); - } - if (assessmentId < 0) { - throw new Error( - `${"CODEQL_ACTION_RISK_ASSESSMENT_ID" /* RISK_ASSESSMENT_ID */} must not be negative: ${rawAssessmentId}` - ); - } - return { sarif: payload.sarif, assessment_id: assessmentId }; -} -var RiskAssessment = { - kind: "risk-assessment" /* RiskAssessment */, - name: "code scanning risk assessment", - target: "PUT /repos/:owner/:repo/code-scanning/risk-assessment" /* RISK_ASSESSMENT */, - sarifExtension: ".csra.sarif", - sarifPredicate: (name) => name.endsWith(RiskAssessment.sarifExtension), - fixCategory: (_2, category) => category, - sentinelPrefix: "CODEQL_UPLOAD_CSRA_SARIF_", - transformPayload: addAssessmentId -}; -function getAnalysisConfig(kind) { - switch (kind) { - case "code-scanning" /* CodeScanning */: - return CodeScanning; - case "code-quality" /* CodeQuality */: - return CodeQuality; - case "risk-assessment" /* RiskAssessment */: - return RiskAssessment; - } -} -var SarifScanOrder = [ - RiskAssessment, - CodeQuality, - CodeScanning -]; - -// src/caching-utils.ts -var crypto2 = __toESM(require("crypto")); -var core7 = __toESM(require_core()); -async function getTotalCacheSize(paths, logger, quiet = false) { - const sizes = await Promise.all( - paths.map((cacheDir2) => tryGetFolderBytes(cacheDir2, logger, quiet)) - ); - return sizes.map((a) => a || 0).reduce((a, b) => a + b, 0); -} -function shouldStoreCache(kind) { - return kind === "full" /* Full */ || kind === "store" /* Store */; -} -function shouldRestoreCache(kind) { - return kind === "full" /* Full */ || kind === "restore" /* Restore */; -} -function getCachingKind(input) { - switch (input) { - case void 0: - case "none": - case "off": - case "false": - return "none" /* None */; - case "full": - case "on": - case "true": - return "full" /* Full */; - case "store": - return "store" /* Store */; - case "restore": - return "restore" /* Restore */; - default: - core7.warning( - `Unrecognized 'dependency-caching' input: ${input}. Defaulting to 'none'.` - ); - return "none" /* None */; - } -} -var cacheKeyHashLength = 16; -function createCacheKeyHash(components) { - const componentsJson = JSON.stringify(components); - return crypto2.createHash("sha256").update(componentsJson).digest("hex").substring(0, cacheKeyHashLength); -} -function getDependencyCachingEnabled() { - const dependencyCaching = getOptionalInput("dependency-caching") || process.env["CODEQL_ACTION_DEPENDENCY_CACHING" /* DEPENDENCY_CACHING */]; - if (dependencyCaching !== void 0) return getCachingKind(dependencyCaching); - if (!isHostedRunner()) return "none" /* None */; - if (!isDefaultSetup()) return "none" /* None */; - return "none" /* None */; -} - -// src/config/db-config.ts -var path6 = __toESM(require("path")); -var jsonschema = __toESM(require_lib2()); -var semver5 = __toESM(require_semver2()); - -// src/error-messages.ts -var PACKS_PROPERTY = "packs"; -function getConfigFileOutsideWorkspaceErrorMessage(configFile) { - return `The configuration file "${configFile}" is outside of the workspace`; -} -function getConfigFileDoesNotExistErrorMessage(configFile) { - return `The configuration file "${configFile}" does not exist`; -} -function getConfigFileParseErrorMessage(configFile, message) { - return `Cannot parse "${configFile}": ${message}`; -} -function getInvalidConfigFileMessage(configFile, messages) { - const andMore = messages.length > 10 ? `, and ${messages.length - 10} more.` : "."; - return `The configuration file "${configFile}" is invalid: ${messages.slice(0, 10).join(", ")}${andMore}`; -} -function getConfigFileRepoFormatInvalidMessage(configFile) { - let error3 = `The configuration file "${configFile}" is not a supported remote file reference.`; - error3 += " Expected format //@"; - return error3; -} -function getConfigFileFormatInvalidMessage(configFile) { - return `The configuration file "${configFile}" could not be read`; -} -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, error3) { - if (configFile === void 0) { - return `The workflow property "${property}" is invalid: ${error3}`; - } else { - return `The configuration file "${configFile}" is invalid: property "${property}" ${error3}`; - } -} -function getRepoPropertyError(propertyName, error3) { - return `The repository property "${propertyName}" is invalid: ${error3}`; -} -function getPacksStrInvalid(packStr, configFile) { - return configFile ? getConfigFilePropertyError( - configFile, - PACKS_PROPERTY, - `"${packStr}" is not a valid pack` - ) : `"${packStr}" is not a valid pack`; -} -function getNoLanguagesError() { - return "Did not detect any languages to analyze. Please update input in workflow or check that GitHub detects the correct languages in your repository."; -} -function getUnknownLanguagesError(languages) { - return `Did not recognize the following languages: ${languages.join(", ")}`; -} - -// src/feature-flags/properties.ts -var GITHUB_CODEQL_PROPERTY_PREFIX = "github-codeql-"; -var RepositoryPropertyName = /* @__PURE__ */ ((RepositoryPropertyName2) => { - RepositoryPropertyName2["CONFIG_FILE"] = "github-codeql-config-file"; - RepositoryPropertyName2["DISABLE_OVERLAY"] = "github-codeql-disable-overlay"; - RepositoryPropertyName2["EXTRA_QUERIES"] = "github-codeql-extra-queries"; - RepositoryPropertyName2["FILE_COVERAGE_ON_PRS"] = "github-codeql-file-coverage-on-prs"; - return RepositoryPropertyName2; -})(RepositoryPropertyName || {}); -function isString2(value) { - return typeof value === "string"; -} -var stringProperty = { - validate: isString2, - parse: parseStringRepositoryProperty -}; -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-disable-overlay" /* DISABLE_OVERLAY */]: booleanProperty, - ["github-codeql-extra-queries" /* EXTRA_QUERIES */]: stringProperty, - ["github-codeql-file-coverage-on-prs" /* FILE_COVERAGE_ON_PRS */]: booleanProperty -}; -async function loadPropertiesFromApi(logger, repositoryNwo) { - try { - const response = await getRepositoryProperties(repositoryNwo); - 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)}` - ); - } - logger.debug( - `Retrieved ${remoteProperties.length} repository properties: ${remoteProperties.map((p) => p.property_name).join(", ")}` - ); - const properties = {}; - const unrecognisedProperties = []; - for (const property of remoteProperties) { - if (property.property_name === void 0) { - throw new Error( - `Expected repository property object to have a 'property_name', but got: ${JSON.stringify(property)}` - ); - } - if (isKnownPropertyName(property.property_name)) { - setProperty(properties, property.property_name, property.value, logger); - } else if (property.property_name.startsWith(GITHUB_CODEQL_PROPERTY_PREFIX) && !isDynamicWorkflow()) { - unrecognisedProperties.push(property.property_name); - } - } - if (Object.keys(properties).length === 0) { - logger.debug("No known repository properties were found."); - } else { - 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}`); - } - } - if (unrecognisedProperties.length > 0) { - const unrecognisedPropertyList = unrecognisedProperties.map((name) => `'${name}'`).join(", "); - logger.warning( - `Found repository properties (${unrecognisedPropertyList}), which look like CodeQL Action repository properties, but which are not understood by this version of the CodeQL Action. Do you need to update to a newer version?` - ); - } - return properties; - } catch (e) { - throw new Error( - `Encountered an error while trying to determine repository properties: ${e}` - ); - } -} -function setProperty(properties, name, value, logger) { - const propertyOptions = repositoryPropertyParsers[name]; - if (propertyOptions.validate(value)) { - properties[name] = propertyOptions.parse(name, value, logger); - } else { - throw new Error( - `Unexpected value for repository property '${name}' (${typeof value}), got: ${JSON.stringify(value)}` - ); - } -} -function parseBooleanRepositoryProperty(name, value, logger) { - if (value !== "true" && value !== "false") { - logger.warning( - `Repository property '${name}' has unexpected value '${value}'. Expected 'true' or 'false'. Defaulting to false.` - ); - } - return value === "true"; -} -function parseStringRepositoryProperty(_name, value) { - return value; -} -var KNOWN_REPOSITORY_PROPERTY_NAMES = new Set( - Object.values(RepositoryPropertyName) -); -function isKnownPropertyName(name) { - return KNOWN_REPOSITORY_PROPERTY_NAMES.has(name); -} - -// src/config/db-config.ts -function shouldCombine(inputValue) { - return !!inputValue?.trim().startsWith("+"); -} -var PACK_IDENTIFIER_PATTERN = (function() { - const alphaNumeric = "[a-z0-9]"; - const alphaNumericDash = "[a-z0-9-]"; - const component = `${alphaNumeric}(${alphaNumericDash}*${alphaNumeric})?`; - return new RegExp(`^${component}/${component}$`); -})(); -function parsePacksSpecification(packStr) { - if (typeof packStr !== "string") { - throw new ConfigurationError(getPacksStrInvalid(packStr)); - } - packStr = packStr.trim(); - const atIndex = packStr.indexOf("@"); - const colonIndex = packStr.indexOf(":", atIndex); - const packStart = 0; - const versionStart = atIndex + 1 || void 0; - const pathStart = colonIndex + 1 || void 0; - const packEnd = Math.min( - atIndex > 0 ? atIndex : Infinity, - colonIndex > 0 ? colonIndex : Infinity, - packStr.length - ); - const versionEnd = versionStart ? Math.min(colonIndex > 0 ? colonIndex : Infinity, packStr.length) : void 0; - const pathEnd = pathStart ? packStr.length : void 0; - const packName = packStr.slice(packStart, packEnd).trim(); - const version = versionStart ? packStr.slice(versionStart, versionEnd).trim() : void 0; - const packPath = pathStart ? packStr.slice(pathStart, pathEnd).trim() : void 0; - if (!PACK_IDENTIFIER_PATTERN.test(packName)) { - throw new ConfigurationError(getPacksStrInvalid(packStr)); - } - if (version) { - try { - new semver5.Range(version); - } catch { - throw new ConfigurationError(getPacksStrInvalid(packStr)); - } - } - if (packPath && (path6.isAbsolute(packPath) || // Permit using "/" instead of "\" on Windows - // Use `x.split(y).join(z)` as a polyfill for `x.replaceAll(y, z)` since - // if we used a regex we'd need to escape the path separator on Windows - // which seems more awkward. - path6.normalize(packPath).split(path6.sep).join("/") !== packPath.split(path6.sep).join("/"))) { - throw new ConfigurationError(getPacksStrInvalid(packStr)); - } - if (!packPath && pathStart) { - throw new ConfigurationError(getPacksStrInvalid(packStr)); - } - return { - name: packName, - version, - path: packPath - }; -} -function validatePackSpecification(pack) { - return prettyPrintPack(parsePacksSpecification(pack)); -} -function parsePacksFromInput(rawPacksInput, languages, packsInputCombines) { - if (!rawPacksInput?.trim()) { - return void 0; +var FEATURE_FLAGS_FILE_NAME = "cached-feature-flags.json"; +var OfflineFeatures = class { + constructor(logger) { + this.logger = logger; } - if (languages.length > 1) { - throw new ConfigurationError( - "Cannot specify a 'packs' input in a multi-language analysis. Use a codeql-config.yml file instead and specify packs by language." - ); - } else if (languages.length === 0) { - throw new ConfigurationError( - "No languages specified. Cannot process the packs input." - ); + logger; + async getEnabledDefaultCliVersions(_variant) { + return { + enabledVersions: [LINKED_CODEQL_VERSION] + }; } - rawPacksInput = rawPacksInput.trim(); - if (packsInputCombines) { - rawPacksInput = rawPacksInput.trim().substring(1).trim(); - if (!rawPacksInput) { - throw new ConfigurationError( - getConfigFilePropertyError( - void 0, - "packs", - "A '+' was used in the 'packs' input to specify that you wished to add some packs to your CodeQL analysis. However, no packs were specified. Please either remove the '+' or specify some packs." - ) - ); + /** + * Gets the `FeatureConfig` for `feature`. + */ + getFeatureConfig(feature) { + return featureConfig[feature]; + } + /** + * Determines whether `feature` is enabled without consulting the GitHub API. + * + * @param feature The feature to check. + * @param codeql An optional CodeQL object. If provided, and a `minimumVersion` is specified for the + * feature, the version of the CodeQL CLI will be checked against the minimum version. + * If the version is less than the minimum version, the feature will be considered + * disabled. If not provided, and a `minimumVersion` is specified for the feature, then + * this function will throw. + * @returns true if the feature is enabled, false otherwise. + * + * @throws if a `minimumVersion` is specified for the feature, and `codeql` is not provided. + */ + async getValue(feature, codeql) { + const offlineValue = await this.getOfflineValue(feature, codeql); + if (offlineValue !== void 0) { + return offlineValue; } + return this.getDefaultValue(feature); } - return { - [languages[0]]: rawPacksInput.split(",").reduce((packs, pack) => { - packs.push(validatePackSpecification(pack)); - return packs; - }, []) - }; -} -async function calculateAugmentation(rawPacksInput, rawQueriesInput, repositoryProperties, languages) { - const packsInputCombines = shouldCombine(rawPacksInput); - const packsInput = parsePacksFromInput( - rawPacksInput, - languages, - packsInputCombines - ); - const queriesInputCombines = shouldCombine(rawQueriesInput); - const queriesInput = parseQueriesFromInput( - rawQueriesInput, - queriesInputCombines - ); - const repoExtraQueries = repositoryProperties["github-codeql-extra-queries" /* EXTRA_QUERIES */]; - const repoExtraQueriesCombines = shouldCombine(repoExtraQueries); - const repoPropertyQueries = { - combines: repoExtraQueriesCombines, - input: parseQueriesFromInput( - repoExtraQueries, - repoExtraQueriesCombines, - new ConfigurationError( - getRepoPropertyError( - "github-codeql-extra-queries" /* EXTRA_QUERIES */, - getEmptyCombinesError() - ) - ) - ) - }; - return { - packsInputCombines, - packsInput: packsInput?.[languages[0]], - queriesInput, - queriesInputCombines, - repoPropertyQueries - }; -} -function parseQueriesFromInput(rawQueriesInput, queriesInputCombines, errorToThrow) { - if (!rawQueriesInput) { + /** + * Determines whether `feature` is enabled using the CLI and environment variables. + */ + async getOfflineValue(feature, codeql) { + const config = this.getFeatureConfig(feature); + if (!codeql && config.minimumVersion) { + throw new Error( + `Internal error: A minimum version is specified for feature ${feature}, but no instance of CodeQL was provided.` + ); + } + if (!codeql && config.toolsFeature) { + throw new Error( + `Internal error: A required tools feature is specified for feature ${feature}, but no instance of CodeQL was provided.` + ); + } + const envVar = (process.env[config.envVar] || "").toLocaleLowerCase(); + if (envVar === "false") { + this.logger.debug( + `Feature ${feature} is disabled via the environment variable ${config.envVar}.` + ); + return false; + } + const minimumVersion2 = config.minimumVersion; + if (codeql && minimumVersion2) { + if (!await codeQlVersionAtLeast(codeql, minimumVersion2)) { + this.logger.debug( + `Feature ${feature} is disabled because the CodeQL CLI version is older than the minimum version ${minimumVersion2}.` + ); + return false; + } else { + this.logger.debug( + `CodeQL CLI version ${(await codeql.getVersion()).version} is newer than the minimum version ${minimumVersion2} for feature ${feature}.` + ); + } + } + const toolsFeature = config.toolsFeature; + if (codeql && toolsFeature) { + if (!await codeql.supportsFeature(toolsFeature)) { + this.logger.debug( + `Feature ${feature} is disabled because the CodeQL CLI version does not support the required tools feature ${toolsFeature}.` + ); + return false; + } else { + this.logger.debug( + `CodeQL CLI version ${(await codeql.getVersion()).version} supports the required tools feature ${toolsFeature} for feature ${feature}.` + ); + } + } + if (envVar === "true") { + this.logger.debug( + `Feature ${feature} is enabled via the environment variable ${config.envVar}.` + ); + return true; + } 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, - "queries", - "A '+' was used in the 'queries' input to specify that you wished to add some packs to your CodeQL analysis. However, no packs were specified. Please either remove the '+' or specify some packs." - ) + /** Gets the default value of `feature`. */ + async getDefaultValue(feature) { + const config = this.getFeatureConfig(feature); + const defaultValue = config.defaultValue; + this.logger.debug( + `Feature ${feature} is ${defaultValue ? "enabled" : "disabled"} due to its default value.` ); + return defaultValue; } - return trimmedInput.split(",").map((query) => ({ uses: query.trim() })); -} -function combineQueries(logger, config, augmentationProperties) { - const result = []; - if (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(", ")}` +}; +var Features = class extends OfflineFeatures { + gitHubFeatureFlags; + constructor(repositoryNwo, tempDir, logger) { + super(logger); + this.gitHubFeatureFlags = new GitHubFeatureFlags( + repositoryNwo, + path5.join(tempDir, FEATURE_FLAGS_FILE_NAME), + logger ); - if (!augmentationProperties.repoPropertyQueries.combines) { - 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); - } } - if (augmentationProperties.queriesInput) { - if (!augmentationProperties.queriesInputCombines) { - return result.concat(augmentationProperties.queriesInput); - } else { - result.push(...augmentationProperties.queriesInput); + async getEnabledDefaultCliVersions(variant) { + if (supportsFeatureFlags(variant)) { + return await this.gitHubFeatureFlags.getEnabledDefaultCliVersionsFromFlags(); } + return super.getEnabledDefaultCliVersions(variant); } - if (config.queries) { - result.push(...config.queries); - } - return result; -} -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; - } - if (augmentationProperties.packsInput) { - if (augmentationProperties.packsInputCombines) { - if (Array.isArray(augmentedConfig.packs)) { - augmentedConfig.packs = (augmentedConfig.packs || []).concat( - augmentationProperties.packsInput - ); - } else if (!augmentedConfig.packs) { - augmentedConfig.packs = augmentationProperties.packsInput; - } else { - const language = Object.keys(augmentedConfig.packs)[0]; - augmentedConfig.packs[language] = augmentedConfig.packs[language].concat(augmentationProperties.packsInput); - } - } else { - augmentedConfig.packs = augmentationProperties.packsInput; + /** + * + * @param feature The feature to check. + * @param codeql An optional CodeQL object. If provided, and a `minimumVersion` is specified for the + * feature, the version of the CodeQL CLI will be checked against the minimum version. + * If the version is less than the minimum version, the feature will be considered + * disabled. If not provided, and a `minimumVersion` is specified for the feature, then + * this function will throw. + * @returns true if the feature is enabled, false otherwise. + * + * @throws if a `minimumVersion` is specified for the feature, and `codeql` is not provided. + */ + async getValue(feature, codeql) { + const offlineValue = await this.getOfflineValue(feature, codeql); + if (offlineValue !== void 0) { + return offlineValue; + } + const apiValue = await this.gitHubFeatureFlags.getValue(feature); + if (apiValue !== void 0) { + this.logger.debug( + `Feature ${feature} is ${apiValue ? "enabled" : "disabled"} via the GitHub API.` + ); + return apiValue; } + return this.getDefaultValue(feature); } - if (Array.isArray(augmentedConfig.packs) && !augmentedConfig.packs.length) { - delete augmentedConfig.packs; +}; +var GitHubFeatureFlags = class { + constructor(repositoryNwo, featureFlagsFile, logger) { + this.repositoryNwo = repositoryNwo; + this.featureFlagsFile = featureFlagsFile; + this.logger = logger; + this.hasAccessedRemoteFeatureFlags = false; } - return augmentedConfig; -} -function parseUserConfig(logger, pathInput, contents, validateConfig) { - try { - const schema = ( - // eslint-disable-next-line @typescript-eslint/no-require-imports - require_db_config_schema() - ); - const doc = load(contents); - if (validateConfig) { - const result = new jsonschema.Validator().validate(doc, schema); - if (result.errors.length > 0) { - for (const error3 of result.errors) { - logger.error(error3.stack); - } - throw new ConfigurationError( - getInvalidConfigFileMessage( - pathInput, - result.errors.map((e) => e.stack) - ) - ); - } + repositoryNwo; + featureFlagsFile; + logger; + cachedApiResponse; + // We cache whether the feature flags were accessed or not in order to accurately report whether flags were + // incorrectly configured vs. inaccessible in our telemetry. + hasAccessedRemoteFeatureFlags; + getCliVersionFromFeatureFlag(f) { + if (!f.startsWith(DEFAULT_VERSION_FEATURE_FLAG_PREFIX) || !f.endsWith(DEFAULT_VERSION_FEATURE_FLAG_SUFFIX)) { + return void 0; } - return doc; - } catch (error3) { - if (error3 instanceof YAMLException) { - throw new ConfigurationError( - getConfigFileParseErrorMessage(pathInput, error3.message) + const version = f.substring( + DEFAULT_VERSION_FEATURE_FLAG_PREFIX.length, + f.length - DEFAULT_VERSION_FEATURE_FLAG_SUFFIX.length + ).replace(/_/g, "."); + if (!semver4.valid(version)) { + this.logger.warning( + `Ignoring feature flag ${f} as it does not specify a valid CodeQL version.` ); + return void 0; } - throw error3; - } -} - -// src/diagnostics.ts -var import_fs = require("fs"); -var import_path = __toESM(require("path")); -var unwrittenDiagnostics = []; -var unwrittenDefaultLanguageDiagnostics = []; -var diagnosticCounter = 0; -function makeDiagnostic(id, name, data = void 0) { - return { - ...data, - timestamp: data?.timestamp ?? (/* @__PURE__ */ new Date()).toISOString(), - source: { ...data?.source, id, name } - }; -} -function addDiagnostic(config, language, diagnostic) { - const logger = getActionsLogger(); - const databasePath = language ? getCodeQLDatabasePath(config, language) : config.dbLocation; - if ((0, import_fs.existsSync)(databasePath)) { - writeDiagnostic(config, language, diagnostic); - } else { - logger.debug( - `Writing a diagnostic for ${language}, but the database at ${databasePath} does not exist yet.` - ); - unwrittenDiagnostics.push({ diagnostic, language }); + return version; } -} -function addNoLanguageDiagnostic(config, diagnostic) { - if (config !== void 0) { - addDiagnostic( - config, - // Arbitrarily choose the first language. We could also choose all languages, but that - // increases the risk of misinterpreting the data. - config.languages[0], - diagnostic + /** + * Returns CLI versions enabled by `default_codeql_version_*_enabled` feature + * flags, sorted from highest to lowest. Falls back to the version pinned in + * `defaults.json` if no such flags are enabled. + */ + async getEnabledDefaultCliVersionsFromFlags() { + const response = await this.getAllFeatures(); + const sortedCliVersions = Object.entries(response).map( + ([f, isEnabled]) => isEnabled ? this.getCliVersionFromFeatureFlag(f) : void 0 + ).filter((f) => f !== void 0).sort(semver4.rcompare); + if (sortedCliVersions.length === 0) { + this.logger.warning( + `Feature flags do not specify a default CLI version. Falling back to the CLI version shipped with the Action. This is ${cliVersion}.` + ); + const result = { + enabledVersions: [LINKED_CODEQL_VERSION] + }; + if (this.hasAccessedRemoteFeatureFlags) { + result.toolsFeatureFlagsValid = false; + } + return result; + } + this.logger.debug( + `Derived default CLI version of ${sortedCliVersions[0]} from feature flags.` ); - } else { - unwrittenDefaultLanguageDiagnostics.push(diagnostic); + return { + enabledVersions: sortedCliVersions.map((cliVersion2) => ({ + cliVersion: cliVersion2, + tagName: `codeql-bundle-v${cliVersion2}` + })), + toolsFeatureFlagsValid: true + }; } -} -function writeDiagnostic(config, language, diagnostic) { - const logger = getActionsLogger(); - const databasePath = language ? getCodeQLDatabasePath(config, language) : config.dbLocation; - const diagnosticsPath = import_path.default.resolve( - databasePath, - "diagnostic", - "codeql-action" - ); - try { - (0, import_fs.mkdirSync)(diagnosticsPath, { recursive: true }); - const uniqueSuffix = (diagnosticCounter++).toString(); - const sanitizedTimestamp = diagnostic.timestamp.replace( - /[^a-zA-Z0-9.-]/g, - "" - ); - const jsonPath = import_path.default.resolve( - diagnosticsPath, - `codeql-action-${sanitizedTimestamp}-${uniqueSuffix}.json` - ); - (0, import_fs.writeFileSync)(jsonPath, JSON.stringify(diagnostic)); - } catch (err) { - logger.warning(`Unable to write diagnostic message to database: ${err}`); - logger.debug(JSON.stringify(diagnostic)); + async getValue(feature) { + const response = await this.getAllFeatures(); + if (response === void 0) { + this.logger.debug(`No feature flags API response for ${feature}.`); + return void 0; + } + const features = response[feature]; + if (features === void 0) { + this.logger.debug(`Feature '${feature}' undefined in API response.`); + return void 0; + } + return !!features; } -} -function logUnwrittenDiagnostics() { - const logger = getActionsLogger(); - const num = unwrittenDiagnostics.length; - if (num > 0) { - logger.warning( - `${num} diagnostic(s) could not be written to the database and will not appear on the Tool Status Page.` - ); - for (const unwritten of unwrittenDiagnostics) { - logger.debug(JSON.stringify(unwritten.diagnostic)); + async getAllFeatures() { + if (this.cachedApiResponse !== void 0) { + return this.cachedApiResponse; + } + const fileFlags = await this.readLocalFlags(); + if (fileFlags !== void 0) { + this.cachedApiResponse = fileFlags; + return fileFlags; + } + let remoteFlags = await this.loadApiResponse(); + if (remoteFlags === void 0) { + remoteFlags = {}; } + this.cachedApiResponse = remoteFlags; + await this.writeLocalFlags(remoteFlags); + return remoteFlags; } -} -function flushDiagnostics(config) { - const logger = getActionsLogger(); - const diagnosticsCount = unwrittenDiagnostics.length + unwrittenDefaultLanguageDiagnostics.length; - logger.debug(`Writing ${diagnosticsCount} diagnostic(s) to database.`); - for (const unwritten of unwrittenDiagnostics) { - writeDiagnostic(config, unwritten.language, unwritten.diagnostic); + async readLocalFlags() { + try { + if (fs5.existsSync(this.featureFlagsFile)) { + this.logger.debug( + `Loading feature flags from ${this.featureFlagsFile}` + ); + return JSON.parse( + fs5.readFileSync(this.featureFlagsFile, "utf8") + ); + } + } catch (e) { + this.logger.warning( + `Error reading cached feature flags file ${this.featureFlagsFile}: ${e}. Requesting from GitHub instead.` + ); + } + return void 0; } - for (const unwritten of unwrittenDefaultLanguageDiagnostics) { - addNoLanguageDiagnostic(config, unwritten); + async writeLocalFlags(flags) { + try { + this.logger.debug(`Writing feature flags to ${this.featureFlagsFile}`); + fs5.writeFileSync(this.featureFlagsFile, JSON.stringify(flags)); + } catch (e) { + this.logger.warning( + `Error writing cached feature flags file ${this.featureFlagsFile}: ${e}.` + ); + } } - unwrittenDiagnostics = []; - unwrittenDefaultLanguageDiagnostics = []; -} -function makeTelemetryDiagnostic(id, name, attributes) { - return makeDiagnostic(id, name, { - attributes, - visibility: { - cliSummaryTable: false, - statusPage: false, - telemetry: true + async loadApiResponse() { + try { + const featuresToRequest = Object.entries(featureConfig).filter( + ([, config]) => !config.legacyApi + ).map(([f]) => f); + const FEATURES_PER_REQUEST = 25; + const featureChunks = []; + while (featuresToRequest.length > 0) { + featureChunks.push(featuresToRequest.splice(0, FEATURES_PER_REQUEST)); + } + let remoteFlags = {}; + for (const chunk of featureChunks) { + const response = await getApiClient().request( + "GET /repos/:owner/:repo/code-scanning/codeql-action/features", + { + owner: this.repositoryNwo.owner, + repo: this.repositoryNwo.repo, + features: chunk.join(",") + } + ); + const chunkFlags = response.data; + remoteFlags = { ...remoteFlags, ...chunkFlags }; + } + this.logger.debug( + "Loaded the following default values for the feature flags from the CodeQL Action API:" + ); + for (const [feature, value] of Object.entries(remoteFlags).sort( + ([nameA], [nameB]) => nameA.localeCompare(nameB) + )) { + this.logger.debug(` ${feature}: ${value}`); + } + this.hasAccessedRemoteFeatureFlags = true; + return remoteFlags; + } catch (e) { + const httpError = asHTTPError(e); + if (httpError?.status === 403) { + this.logger.warning( + `This run of the CodeQL Action does not have permission to access the CodeQL Action API endpoints. As a result, it will not be opted into any experimental features. This could be because the Action is running on a pull request from a fork. If not, please ensure the workflow has at least the 'security-events: read' permission. Details: ${httpError.message}` + ); + this.hasAccessedRemoteFeatureFlags = false; + return {}; + } else { + throw new Error( + `Encountered an error while trying to determine feature enablement: ${e}` + ); + } } - }); -} - -// src/diff-informed-analysis-utils.ts -var fs6 = __toESM(require("fs")); -async function getDiffInformedAnalysisBranches(codeql, features, logger) { - if (!await features.getValue("diff_informed_queries" /* DiffInformedQueries */, codeql)) { - return void 0; - } - const gitHubVersion = await getGitHubVersion(); - if (gitHubVersion.type === "GitHub Enterprise Server" /* GHES */ && satisfiesGHESVersion(gitHubVersion.version, "<3.19", true)) { - return void 0; } - const branches = getPullRequestBranches(); - if (!branches) { - logger.info( - "Not performing diff-informed analysis because we are not analyzing a pull request." +}; +function supportsFeatureFlags(githubVariant) { + return githubVariant === "GitHub.com" /* DOTCOM */ || githubVariant === "GitHub Enterprise Cloud with data residency" /* GHEC_DR */; +} +function initFeatures(gitHubVersion, repositoryNwo, tempDir, logger) { + if (!supportsFeatureFlags(gitHubVersion.type)) { + logger.debug( + "Not running against github.com. Using default values for all features." ); + return new OfflineFeatures(logger); + } else { + return new Features(repositoryNwo, tempDir, logger); } - return branches; } -async function prepareDiffInformedAnalysis(codeql, features, logger) { - let branches; - try { - branches = await getDiffInformedAnalysisBranches(codeql, features, logger); - } catch (e) { - logger.warning( - `Failed to determine branch information for diff-informed analysis: ${getErrorMessage(e)}` + +// src/analyses.ts +var AnalysisKind = /* @__PURE__ */ ((AnalysisKind2) => { + AnalysisKind2["CodeScanning"] = "code-scanning"; + AnalysisKind2["CodeQuality"] = "code-quality"; + AnalysisKind2["RiskAssessment"] = "risk-assessment"; + return AnalysisKind2; +})(AnalysisKind || {}); +var compatibilityMatrix = { + ["code-scanning" /* CodeScanning */]: /* @__PURE__ */ new Set(["code-quality" /* CodeQuality */]), + ["code-quality" /* CodeQuality */]: /* @__PURE__ */ new Set(["code-scanning" /* CodeScanning */]), + ["risk-assessment" /* RiskAssessment */]: /* @__PURE__ */ new Set() +}; +var supportedAnalysisKinds = new Set(Object.values(AnalysisKind)); +async function parseAnalysisKinds(input) { + const components = input.split(","); + if (components.length < 1) { + throw new ConfigurationError( + "At least one analysis kind must be configured." ); - return false; - } - if (!branches) { - return false; } - try { - return await computeAndPersistDiffRanges(branches, logger); - } catch (e) { - logger.warning( - `Failed to compute diff-informed analysis ranges: ${getErrorMessage(e)}` - ); - return false; + for (const component of components) { + if (!supportedAnalysisKinds.has(component)) { + throw new ConfigurationError(`Unknown analysis kind: ${component}`); + } } -} -function writeDiffRangesJsonFile(logger, ranges) { - const jsonContents = JSON.stringify(ranges, null, 2); - const jsonFilePath = getDiffRangesJsonFilePath(); - fs6.writeFileSync(jsonFilePath, jsonContents); - logger.debug( - `Wrote pr-diff-range JSON file to ${jsonFilePath}: -${jsonContents}` + return Array.from( + new Set(components.map((component) => component)) ); } -function readDiffRangesJsonFile(logger) { - const jsonFilePath = getDiffRangesJsonFilePath(); - if (!fs6.existsSync(jsonFilePath)) { - logger.debug(`Diff ranges JSON file does not exist at ${jsonFilePath}`); - return void 0; +var cachedAnalysisKinds; +function isOnlyCodeScanningEnabled(analysisKinds) { + return analysisKinds.length === 1 && analysisKinds[0] === "code-scanning" /* CodeScanning */; +} +function makeAnalysisKindUsageError(message) { + return `The \`analysis-kinds\` input is experimental and for GitHub-internal use only. Its behaviour may change at any time or be removed entirely. ${message}`; +} +async function getAnalysisKinds(logger, features, skipCache = false) { + if (!skipCache && cachedAnalysisKinds !== void 0) { + return cachedAnalysisKinds; } - const jsonContents = fs6.readFileSync(jsonFilePath, "utf8"); - logger.debug( - `Read pr-diff-range JSON file from ${jsonFilePath}: -${jsonContents}` + const analysisKinds = await parseAnalysisKinds( + getRequiredInput("analysis-kinds") ); - try { - return JSON.parse(jsonContents); - } catch (e) { - logger.warning( - `Failed to parse diff ranges JSON file at ${jsonFilePath}: ${e}` + if (!isInTestMode() && !isDynamicWorkflow() && !isOnlyCodeScanningEnabled(analysisKinds)) { + const codeQualityHint = analysisKinds.includes("code-quality" /* CodeQuality */) ? " If your intention is to use quality queries outside of Code Quality, use the `queries` input with `code-quality` instead." : ""; + logger.error( + makeAnalysisKindUsageError( + `An analysis kind other than \`code-scanning\` was specified in a custom workflow. This is not supported and will become a fatal error in a future version of the CodeQL Action.${codeQualityHint}` + ) ); - return void 0; - } -} -async function getPullRequestEditedDiffRanges(branches, logger) { - const fileDiffs = await getFileDiffsWithBasehead(branches, logger); - if (fileDiffs === void 0) { - return void 0; } - if (fileDiffs.length >= 300) { + const qualityQueriesInput = getOptionalInput("quality-queries"); + if (qualityQueriesInput !== void 0) { logger.warning( - `Cannot retrieve the full diff because there are too many (${fileDiffs.length}) changed files in the pull request.` + "The `quality-queries` input is deprecated and will be removed in a future version of the CodeQL Action. Use the `analysis-kinds` input to configure different analysis kinds instead." ); - return void 0; } - const results = []; - for (const filediff of fileDiffs) { - const diffRanges = getDiffRanges(filediff, logger); - if (diffRanges === void 0) { - return void 0; + if (!analysisKinds.includes("code-quality" /* CodeQuality */) && qualityQueriesInput !== void 0) { + analysisKinds.push("code-quality" /* CodeQuality */); + } + for (const analysisKind of analysisKinds) { + for (const otherAnalysisKind of analysisKinds) { + if (analysisKind === otherAnalysisKind) continue; + if (!compatibilityMatrix[analysisKind].has(otherAnalysisKind)) { + throw new ConfigurationError( + `${analysisKind} and ${otherAnalysisKind} cannot be enabled at the same time` + ); + } } - results.push(...diffRanges); } - return results; -} -async function computeAndPersistDiffRanges(branches, logger) { - logger.info("Computing PR diff ranges..."); - const ranges = await getPullRequestEditedDiffRanges(branches, logger); - if (ranges === void 0) { - return false; + if (!isInTestMode() && analysisKinds.length > 1 && !await features.getValue("allow_multiple_analysis_kinds" /* AllowMultipleAnalysisKinds */)) { + logger.error( + makeAnalysisKindUsageError( + "Specifying multiple values as input is no longer supported. Continuing with only `analysis-kinds: code-scanning`." + ) + ); + cachedAnalysisKinds = ["code-scanning" /* CodeScanning */]; + return cachedAnalysisKinds; } - writeDiffRangesJsonFile(logger, ranges); - const distinctFiles = new Set(ranges.map((r) => r.path)).size; - logger.info( - `Persisted ${ranges.length} diff range(s) across ${distinctFiles} file(s).` - ); - return true; + cachedAnalysisKinds = analysisKinds; + return cachedAnalysisKinds; } -async function getFileDiffsWithBasehead(branches, logger) { - const repositoryNwo = getRepositoryNwoFromEnv( - "CODE_SCANNING_REPOSITORY", - "GITHUB_REPOSITORY" - ); - const basehead = `${branches.base}...${branches.head}`; - try { - const response = await getApiClient().rest.repos.compareCommitsWithBasehead( - { - owner: repositoryNwo.owner, - repo: repositoryNwo.repo, - basehead, - per_page: 1 - } +var codeQualityQueries = ["code-quality"]; +var CodeScanning = { + kind: "code-scanning" /* CodeScanning */, + name: "code scanning", + target: "PUT /repos/:owner/:repo/code-scanning/analysis" /* CODE_SCANNING */, + sarifExtension: ".sarif", + sarifPredicate: (name) => name.endsWith(CodeScanning.sarifExtension) && !CodeQuality.sarifPredicate(name) && !RiskAssessment.sarifPredicate(name), + fixCategory: (_2, category) => category, + sentinelPrefix: "CODEQL_UPLOAD_SARIF_", + transformPayload: (payload) => payload +}; +var CodeQuality = { + kind: "code-quality" /* CodeQuality */, + name: "code quality", + target: "PUT /repos/:owner/:repo/code-quality/analysis" /* CODE_QUALITY */, + sarifExtension: ".quality.sarif", + sarifPredicate: (name) => name.endsWith(CodeQuality.sarifExtension), + fixCategory: fixCodeQualityCategory, + sentinelPrefix: "CODEQL_UPLOAD_QUALITY_SARIF_", + transformPayload: (payload) => payload +}; +function addAssessmentId(payload) { + const rawAssessmentId = getRequiredEnvParam("CODEQL_ACTION_RISK_ASSESSMENT_ID" /* RISK_ASSESSMENT_ID */); + const assessmentId = parseInt(rawAssessmentId, 10); + if (Number.isNaN(assessmentId)) { + throw new Error( + `${"CODEQL_ACTION_RISK_ASSESSMENT_ID" /* RISK_ASSESSMENT_ID */} must not be NaN: ${rawAssessmentId}` ); - logger.debug( - `Response from compareCommitsWithBasehead(${basehead}): -${JSON.stringify(response, null, 2)}` + } + if (assessmentId < 0) { + throw new Error( + `${"CODEQL_ACTION_RISK_ASSESSMENT_ID" /* RISK_ASSESSMENT_ID */} must not be negative: ${rawAssessmentId}` ); - return response.data.files; - } catch (error3) { - if (error3.status) { - logger.warning(`Error retrieving diff ${basehead}: ${error3.message}`); - logger.debug( - `Error running compareCommitsWithBasehead(${basehead}): -Request: ${JSON.stringify(error3.request, null, 2)} -Error Response: ${JSON.stringify(error3.response, null, 2)}` - ); - return void 0; + } + return { sarif: payload.sarif, assessment_id: assessmentId }; +} +var RiskAssessment = { + kind: "risk-assessment" /* RiskAssessment */, + name: "code scanning risk assessment", + target: "PUT /repos/:owner/:repo/code-scanning/risk-assessment" /* RISK_ASSESSMENT */, + sarifExtension: ".csra.sarif", + sarifPredicate: (name) => name.endsWith(RiskAssessment.sarifExtension), + fixCategory: (_2, category) => category, + sentinelPrefix: "CODEQL_UPLOAD_CSRA_SARIF_", + transformPayload: addAssessmentId +}; +function getAnalysisConfig(kind) { + switch (kind) { + case "code-scanning" /* CodeScanning */: + return CodeScanning; + case "code-quality" /* CodeQuality */: + return CodeQuality; + case "risk-assessment" /* RiskAssessment */: + return RiskAssessment; + } +} +var SarifScanOrder = [ + RiskAssessment, + CodeQuality, + CodeScanning +]; + +// src/analyze.ts +var fs16 = __toESM(require("fs")); +var path15 = __toESM(require("path")); +var import_perf_hooks3 = require("perf_hooks"); +var io5 = __toESM(require_io()); + +// src/autobuild.ts +var core13 = __toESM(require_core()); + +// src/codeql.ts +var fs15 = __toESM(require("fs")); +var path14 = __toESM(require("path")); +var core12 = __toESM(require_core()); +var toolrunner3 = __toESM(require_toolrunner()); + +// src/cli-errors.ts +var SUPPORTED_PLATFORMS = [ + ["linux", "x64"], + ["win32", "x64"], + ["darwin", "x64"], + ["darwin", "arm64"] +]; +var CliError = class extends Error { + exitCode; + stderr; + constructor({ cmd, args, exitCode, stderr }) { + const prettyCommand = prettyPrintInvocation(cmd, args); + const fatalErrors = extractFatalErrors(stderr); + const autobuildErrors = extractAutobuildErrors(stderr); + let message; + if (fatalErrors) { + message = `Encountered a fatal error while running "${prettyCommand}". Exit code was ${exitCode} and error was: ${ensureEndsInPeriod( + fatalErrors.trim() + )} See the logs for more details.`; + } else if (autobuildErrors) { + message = `We were unable to automatically build your code. Please provide manual build steps. See ${"https://docs.github.com/en/code-security/code-scanning/troubleshooting-code-scanning/automatic-build-failed" /* AUTOMATIC_BUILD_FAILED */} for more information. Encountered the following error: ${autobuildErrors}`; } else { - throw error3; + const lastLine = ensureEndsInPeriod( + stderr.trim().split("\n").pop()?.trim() || "n/a" + ); + message = `Encountered a fatal error while running "${prettyCommand}". Exit code was ${exitCode} and last log line was: ${lastLine} See the logs for more details.`; } + super(message); + this.exitCode = exitCode; + this.stderr = stderr; } -} -function getDiffRanges(fileDiff, logger) { - if (fileDiff.patch === void 0) { - if (fileDiff.changes === 0) { - return []; +}; +function extractFatalErrors(error3) { + const fatalErrorRegex = /.*fatal (internal )?error occurr?ed(. Details)?:/gi; + let fatalErrors = []; + let lastFatalErrorIndex; + let match2; + while ((match2 = fatalErrorRegex.exec(error3)) !== null) { + if (lastFatalErrorIndex !== void 0) { + fatalErrors.push(error3.slice(lastFatalErrorIndex, match2.index).trim()); } - return [ - { - path: fileDiff.filename, - startLine: 0, - endLine: 0 - } - ]; + lastFatalErrorIndex = match2.index; } - let currentLine = 0; - let additionRangeStartLine = void 0; - const diffRanges = []; - const diffLines = fileDiff.patch.split("\n"); - diffLines.push(" "); - for (const diffLine of diffLines) { - if (diffLine.startsWith("-")) { - continue; + if (lastFatalErrorIndex !== void 0) { + const lastError = error3.slice(lastFatalErrorIndex).trim(); + if (fatalErrors.length === 0) { + return lastError; } - if (diffLine.startsWith("+")) { - if (additionRangeStartLine === void 0) { - additionRangeStartLine = currentLine; - } - currentLine++; - continue; + const isOneLiner = !fatalErrors.some((e) => e.includes("\n")); + if (isOneLiner) { + fatalErrors = fatalErrors.map(ensureEndsInPeriod); } - if (additionRangeStartLine !== void 0) { - diffRanges.push({ - path: fileDiff.filename, - startLine: additionRangeStartLine, - endLine: currentLine - 1 - }); - additionRangeStartLine = void 0; + return [ + ensureEndsInPeriod(lastError), + "Context:", + ...fatalErrors.reverse() + ].join(isOneLiner ? " " : "\n"); + } + return void 0; +} +function extractAutobuildErrors(error3) { + const pattern = /.*\[autobuild\] \[ERROR\] (.*)/gi; + let errorLines = [...error3.matchAll(pattern)].map((match2) => match2[1]); + if (errorLines.length > 10) { + errorLines = errorLines.slice(0, 10); + errorLines.push("(truncated)"); + } + return errorLines.join("\n") || void 0; +} +var cliErrorsConfig = { + ["AutobuildError" /* AutobuildError */]: { + cliErrorMessageCandidates: [ + new RegExp("We were unable to automatically build your code") + ] + }, + ["CouldNotCreateTempDir" /* CouldNotCreateTempDir */]: { + cliErrorMessageCandidates: [new RegExp("Could not create temp directory")] + }, + ["ExternalRepositoryCloneFailed" /* ExternalRepositoryCloneFailed */]: { + cliErrorMessageCandidates: [ + new RegExp("Failed to clone external Git repository") + ] + }, + ["GradleBuildFailed" /* GradleBuildFailed */]: { + cliErrorMessageCandidates: [ + new RegExp("\\[autobuild\\] FAILURE: Build failed with an exception.") + ] + }, + // Version of CodeQL CLI is incompatible with this version of the CodeQL Action + ["IncompatibleWithActionVersion" /* IncompatibleWithActionVersion */]: { + cliErrorMessageCandidates: [ + new RegExp("is not compatible with this CodeQL CLI") + ] + }, + ["InitCalledTwice" /* InitCalledTwice */]: { + cliErrorMessageCandidates: [ + new RegExp( + "Refusing to create databases .* but could not process any of it" + ) + ], + additionalErrorMessageToAppend: `Is the "init" action called twice in the same job?` + }, + ["InvalidConfigFile" /* InvalidConfigFile */]: { + cliErrorMessageCandidates: [ + new RegExp("Config file .* is not valid"), + new RegExp("The supplied config file is empty") + ] + }, + ["InvalidExternalRepoSpecifier" /* InvalidExternalRepoSpecifier */]: { + cliErrorMessageCandidates: [ + new RegExp("Specifier for external repository is invalid") + ] + }, + // Expected source location for database creation does not exist + ["InvalidSourceRoot" /* InvalidSourceRoot */]: { + cliErrorMessageCandidates: [new RegExp("Invalid source root")] + }, + ["MavenBuildFailed" /* MavenBuildFailed */]: { + cliErrorMessageCandidates: [ + new RegExp("\\[autobuild\\] \\[ERROR\\] Failed to execute goal") + ] + }, + ["NoBuildCommandAutodetected" /* NoBuildCommandAutodetected */]: { + cliErrorMessageCandidates: [ + new RegExp("Could not auto-detect a suitable build method") + ] + }, + ["NoBuildMethodAutodetected" /* NoBuildMethodAutodetected */]: { + cliErrorMessageCandidates: [ + new RegExp( + "Could not detect a suitable build command for the source checkout" + ) + ] + }, + // Usually when a manual build script has failed, or if an autodetected language + // was unintended to have CodeQL analysis run on it. + ["NoSourceCodeSeen" /* NoSourceCodeSeen */]: { + exitCode: 32, + cliErrorMessageCandidates: [ + new RegExp( + "CodeQL detected code written in .* but could not process any of it" + ), + new RegExp( + "CodeQL did not detect any code written in languages supported by CodeQL" + ) + ] + }, + ["NoSupportedBuildCommandSucceeded" /* NoSupportedBuildCommandSucceeded */]: { + cliErrorMessageCandidates: [ + new RegExp("No supported build command succeeded") + ] + }, + ["NoSupportedBuildSystemDetected" /* NoSupportedBuildSystemDetected */]: { + cliErrorMessageCandidates: [ + new RegExp("No supported build system detected") + ] + }, + ["OutOfMemoryOrDisk" /* OutOfMemoryOrDisk */]: { + cliErrorMessageCandidates: [ + new RegExp("CodeQL is out of memory."), + new RegExp("out of disk"), + new RegExp("No space left on device") + ], + additionalErrorMessageToAppend: "For more information, see https://gh.io/troubleshooting-code-scanning/out-of-disk-or-memory" + }, + ["PackCannotBeFound" /* PackCannotBeFound */]: { + cliErrorMessageCandidates: [ + new RegExp( + "Query pack .* cannot be found\\. Check the spelling of the pack\\." + ), + new RegExp( + "is not a .ql file, .qls file, a directory, or a query pack specification." + ) + ] + }, + ["PackMissingAuth" /* PackMissingAuth */]: { + cliErrorMessageCandidates: [ + new RegExp("GitHub Container registry .* 403 Forbidden"), + new RegExp( + "Do you need to specify a token to authenticate to the registry?" + ) + ] + }, + ["SwiftBuildFailed" /* SwiftBuildFailed */]: { + cliErrorMessageCandidates: [ + new RegExp( + "\\[autobuilder/build\\] \\[build-command-failed\\] `autobuild` failed to run the build command" + ) + ] + }, + ["SwiftIncompatibleOs" /* SwiftIncompatibleOs */]: { + cliErrorMessageCandidates: [ + new RegExp("\\[incompatible-os\\]"), + new RegExp("Swift analysis is only supported on macOS") + ] + }, + ["UnsupportedBuildMode" /* UnsupportedBuildMode */]: { + cliErrorMessageCandidates: [ + new RegExp( + "does not support the .* build mode. Please try using one of the following build modes instead" + ) + ] + }, + ["NotFoundInRegistry" /* NotFoundInRegistry */]: { + cliErrorMessageCandidates: [ + new RegExp("'.*' not found in the registry '.*'") + ] + } +}; +function getCliConfigCategoryIfExists(cliError) { + for (const [category, configuration] of Object.entries(cliErrorsConfig)) { + if (cliError.exitCode !== void 0 && configuration.exitCode !== void 0 && cliError.exitCode === configuration.exitCode) { + return category; } - if (diffLine.startsWith("@@ ")) { - const match2 = diffLine.match(/^@@ -\d+(?:,\d+)? \+(\d+)(?:,\d+)? @@/); - if (match2 === null) { - logger.warning( - `Cannot parse diff hunk header for ${fileDiff.filename}: ${diffLine}` - ); - return void 0; + for (const e of configuration.cliErrorMessageCandidates) { + if (cliError.message.match(e) || cliError.stderr.match(e)) { + return category; } - currentLine = parseInt(match2[1], 10); - continue; - } - if (diffLine.startsWith(" ")) { - currentLine++; - continue; } } - return diffRanges; + return void 0; } - -// src/languages/builtin.json -var builtin_default = { - languages: [ - "actions", - "cpp", - "csharp", - "go", - "java", - "javascript", - "python", - "ruby", - "rust", - "swift" - ], - aliases: { - c: "cpp", - "c-c++": "cpp", - "c-cpp": "cpp", - "c#": "csharp", - "c++": "cpp", - "java-kotlin": "java", - "javascript-typescript": "javascript", - kotlin: "java", - typescript: "javascript" - } -}; - -// src/languages/index.ts -var BuiltInLanguage = /* @__PURE__ */ ((BuiltInLanguage3) => { - BuiltInLanguage3["actions"] = "actions"; - BuiltInLanguage3["cpp"] = "cpp"; - BuiltInLanguage3["csharp"] = "csharp"; - BuiltInLanguage3["go"] = "go"; - BuiltInLanguage3["java"] = "java"; - BuiltInLanguage3["javascript"] = "javascript"; - BuiltInLanguage3["python"] = "python"; - BuiltInLanguage3["ruby"] = "ruby"; - BuiltInLanguage3["rust"] = "rust"; - BuiltInLanguage3["swift"] = "swift"; - return BuiltInLanguage3; -})(BuiltInLanguage || {}); -var builtInLanguageSet = new Set(builtin_default.languages); -function isBuiltInLanguage(language) { - return builtInLanguageSet.has(language); +function isUnsupportedPlatform() { + return !SUPPORTED_PLATFORMS.some( + ([platform2, arch2]) => platform2 === process.platform && arch2 === process.arch + ); } -function parseBuiltInLanguage(language) { - language = language.trim().toLowerCase(); - language = builtin_default.aliases[language] ?? language; - if (isBuiltInLanguage(language)) { - return language; +function getUnsupportedPlatformError(cliError) { + return new ConfigurationError( + `The CodeQL CLI does not support the platform/architecture combination of ${process.platform}/${process.arch} (see ${"https://codeql.github.com/docs/codeql-overview/system-requirements/" /* SYSTEM_REQUIREMENTS */}). The underlying error was: ${cliError.message}` + ); +} +function wrapCliConfigurationError(cliError) { + if (isUnsupportedPlatform()) { + return getUnsupportedPlatformError(cliError); } - return void 0; + const cliConfigErrorCategory = getCliConfigCategoryIfExists(cliError); + if (cliConfigErrorCategory === void 0) { + return cliError; + } + let errorMessageBuilder = cliError.message; + const additionalErrorMessageToAppend = cliErrorsConfig[cliConfigErrorCategory].additionalErrorMessageToAppend; + if (additionalErrorMessageToAppend !== void 0) { + errorMessageBuilder = `${errorMessageBuilder} ${additionalErrorMessageToAppend}`; + } + return new ConfigurationError(errorMessageBuilder); } -// src/overlay/diagnostics.ts -async function addOverlayDisablementDiagnostics(config, codeql, overlayDisabledReason) { - addNoLanguageDiagnostic( - config, - makeTelemetryDiagnostic( - "codeql-action/overlay-disabled", - "Overlay analysis disabled", - { - reason: overlayDisabledReason - } - ) - ); - if (overlayDisabledReason === "skipped-due-to-cached-status" /* SkippedDueToCachedStatus */) { - addNoLanguageDiagnostic( - config, - makeDiagnostic( - "codeql-action/overlay-disabled-due-to-cached-status", - "Skipped improved incremental analysis because it failed previously with similar hardware resources", - { - attributes: { - languages: config.languages - }, - markdownMessage: `Improved incremental analysis was skipped because it previously failed for this repository with CodeQL version ${(await codeql.getVersion()).version} on a runner with similar hardware resources. One possible reason for this is that improved incremental analysis can require a significant amount of disk space for some repositories. If you want to try re-enabling improved incremental analysis, increase the disk space available to the runner. If that doesn't help, contact GitHub Support for further assistance. +// src/config-utils.ts +var fs9 = __toESM(require("fs")); +var path10 = __toESM(require("path")); +var import_perf_hooks = require("perf_hooks"); +var core10 = __toESM(require_core()); -Improved incremental analysis will be automatically retried when the next version of CodeQL is released. You can also manually trigger a retry by [removing](${"https://docs.github.com/en/actions/how-tos/manage-workflow-runs/manage-caches#deleting-cache-entries" /* DELETE_ACTIONS_CACHE_ENTRIES */}) \`codeql-overlay-status-*\` entries from the Actions cache.`, - severity: "note", - visibility: { - cliSummaryTable: true, - statusPage: true, - telemetry: false - } - } - ) - ); - } - if (overlayDisabledReason === "disabled-by-repository-property" /* DisabledByRepositoryProperty */) { - addNoLanguageDiagnostic( - config, - makeDiagnostic( - "codeql-action/overlay-disabled-by-repository-property", - "Improved incremental analysis disabled by repository property", - { - attributes: { - languages: config.languages - }, - markdownMessage: `Improved incremental analysis has been disabled because the \`${"github-codeql-disable-overlay" /* DISABLE_OVERLAY */}\` repository property is set to \`true\`. To re-enable improved incremental analysis, set this property to \`false\` or remove it.`, - severity: "note", - visibility: { - cliSummaryTable: true, - statusPage: true, - telemetry: false - } - } - ) - ); +// src/caching-utils.ts +var crypto2 = __toESM(require("crypto")); +var core9 = __toESM(require_core()); +async function getTotalCacheSize(paths, logger, quiet = false) { + const sizes = await Promise.all( + paths.map((cacheDir2) => tryGetFolderBytes(cacheDir2, logger, quiet)) + ); + return sizes.map((a) => a || 0).reduce((a, b) => a + b, 0); +} +function shouldStoreCache(kind) { + return kind === "full" /* Full */ || kind === "store" /* Store */; +} +function shouldRestoreCache(kind) { + return kind === "full" /* Full */ || kind === "restore" /* Restore */; +} +function getCachingKind(input) { + switch (input) { + case void 0: + case "none": + case "off": + case "false": + return "none" /* None */; + case "full": + case "on": + case "true": + return "full" /* Full */; + case "store": + return "store" /* Store */; + case "restore": + return "restore" /* Restore */; + default: + core9.warning( + `Unrecognized 'dependency-caching' input: ${input}. Defaulting to 'none'.` + ); + return "none" /* None */; } } +var cacheKeyHashLength = 16; +function createCacheKeyHash(components) { + const componentsJson = JSON.stringify(components); + return crypto2.createHash("sha256").update(componentsJson).digest("hex").substring(0, cacheKeyHashLength); +} +function getDependencyCachingEnabled() { + const dependencyCaching = getOptionalInput("dependency-caching") || process.env["CODEQL_ACTION_DEPENDENCY_CACHING" /* DEPENDENCY_CACHING */]; + if (dependencyCaching !== void 0) return getCachingKind(dependencyCaching); + if (!isHostedRunner()) return "none" /* None */; + if (!isDefaultSetup()) return "none" /* None */; + return "none" /* None */; +} -// src/overlay/status.ts -var fs7 = __toESM(require("fs")); -var path8 = __toESM(require("path")); -var actionsCache = __toESM(require_cache4()); -var MAX_CACHE_OPERATION_MS = 3e4; -var STATUS_FILE_NAME = "overlay-status.json"; -function getStatusFilePath(languages) { - return path8.join( - getTemporaryDirectory(), - "overlay-status", - [...languages].sort().join("+"), - STATUS_FILE_NAME - ); +// src/config/db-config.ts +var path6 = __toESM(require("path")); +var jsonschema = __toESM(require_lib2()); +var semver5 = __toESM(require_semver2()); + +// src/error-messages.ts +var PACKS_PROPERTY = "packs"; +function getConfigFileOutsideWorkspaceErrorMessage(configFile) { + return `The configuration file "${configFile}" is outside of the workspace`; +} +function getConfigFileDoesNotExistErrorMessage(configFile) { + return `The configuration file "${configFile}" does not exist`; +} +function getConfigFileParseErrorMessage(configFile, message) { + return `Cannot parse "${configFile}": ${message}`; +} +function getInvalidConfigFileMessage(configFile, messages) { + const andMore = messages.length > 10 ? `, and ${messages.length - 10} more.` : "."; + return `The configuration file "${configFile}" is invalid: ${messages.slice(0, 10).join(", ")}${andMore}`; +} +function getConfigFileRepoOldFormatInvalidMessage(configFile) { + let error3 = `The configuration file "${configFile}" is not a supported remote file reference.`; + error3 += " Expected format //@"; + return error3; +} +function getConfigFileRepoFormatInvalidMessage(configFile) { + let error3 = `The configuration file "${configFile}" is not a supported remote file reference.`; + error3 += " Expected format [/][@][:]"; + return error3; +} +function getConfigFileFormatInvalidMessage(configFile) { + return `The configuration file "${configFile}" could not be read`; +} +function getConfigFileDirectoryGivenMessage(configFile) { + return `The configuration file "${configFile}" looks like a directory, not a file`; } -function createOverlayStatus(attributes, checkRunId) { - const job = { - workflowRunId: getWorkflowRunID(), - workflowRunAttempt: getWorkflowRunAttempt(), - name: getRequiredEnvParam("GITHUB_JOB"), - checkRunId - }; - return { - ...attributes, - job - }; +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.`; } -async function shouldSkipOverlayAnalysis(codeql, languages, diskUsage, logger) { - const status = await getOverlayStatus(codeql, languages, diskUsage, logger); - if (status === void 0) { - return false; - } - if (status.attemptedToBuildOverlayBaseDatabase && !status.builtOverlayBaseDatabase) { - logger.debug( - "Cached overlay status indicates that building an overlay base database was unsuccessful." - ); - return true; +function getConfigFilePropertyError(configFile, property, error3) { + if (configFile === void 0) { + return `The workflow property "${property}" is invalid: ${error3}`; + } else { + return `The configuration file "${configFile}" is invalid: property "${property}" ${error3}`; } - logger.debug( - "Cached overlay status does not indicate a previous unsuccessful attempt to build an overlay base database." - ); - return false; } -async function getOverlayStatus(codeql, languages, diskUsage, logger) { - const cacheKey3 = await getCacheKey(codeql, languages, diskUsage); - const statusFile = getStatusFilePath(languages); +function getRepoPropertyError(propertyName, error3) { + return `The repository property "${propertyName}" is invalid: ${error3}`; +} +function getPacksStrInvalid(packStr, configFile) { + return configFile ? getConfigFilePropertyError( + configFile, + PACKS_PROPERTY, + `"${packStr}" is not a valid pack` + ) : `"${packStr}" is not a valid pack`; +} +function getNoLanguagesError() { + return "Did not detect any languages to analyze. Please update input in workflow or check that GitHub detects the correct languages in your repository."; +} +function getUnknownLanguagesError(languages) { + return `Did not recognize the following languages: ${languages.join(", ")}`; +} + +// src/feature-flags/properties.ts +var GITHUB_CODEQL_PROPERTY_PREFIX = "github-codeql-"; +var RepositoryPropertyName = /* @__PURE__ */ ((RepositoryPropertyName2) => { + RepositoryPropertyName2["CONFIG_FILE"] = "github-codeql-config-file"; + RepositoryPropertyName2["DISABLE_OVERLAY"] = "github-codeql-disable-overlay"; + RepositoryPropertyName2["EXTRA_QUERIES"] = "github-codeql-extra-queries"; + RepositoryPropertyName2["FILE_COVERAGE_ON_PRS"] = "github-codeql-file-coverage-on-prs"; + return RepositoryPropertyName2; +})(RepositoryPropertyName || {}); +function isString2(value) { + return typeof value === "string"; +} +var stringProperty = { + validate: isString2, + parse: parseStringRepositoryProperty +}; +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-disable-overlay" /* DISABLE_OVERLAY */]: booleanProperty, + ["github-codeql-extra-queries" /* EXTRA_QUERIES */]: stringProperty, + ["github-codeql-file-coverage-on-prs" /* FILE_COVERAGE_ON_PRS */]: booleanProperty +}; +async function loadPropertiesFromApi(logger, repositoryNwo) { try { - await fs7.promises.mkdir(path8.dirname(statusFile), { recursive: true }); - const foundKey = await waitForResultWithTimeLimit( - MAX_CACHE_OPERATION_MS, - actionsCache.restoreCache([statusFile], cacheKey3), - () => { - logger.warning("Timed out restoring overlay status from cache."); - } + const response = await getRepositoryProperties(repositoryNwo); + 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)}` + ); + } + logger.debug( + `Retrieved ${remoteProperties.length} repository properties: ${remoteProperties.map((p) => p.property_name).join(", ")}` ); - if (foundKey === void 0) { - logger.debug("No overlay status found in Actions cache."); - return void 0; + const properties = {}; + const unrecognisedProperties = []; + for (const property of remoteProperties) { + if (property.property_name === void 0) { + throw new Error( + `Expected repository property object to have a 'property_name', but got: ${JSON.stringify(property)}` + ); + } + if (isKnownPropertyName(property.property_name)) { + setProperty(properties, property.property_name, property.value, logger); + } else if (property.property_name.startsWith(GITHUB_CODEQL_PROPERTY_PREFIX) && !isDynamicWorkflow()) { + unrecognisedProperties.push(property.property_name); + } } - if (!fs7.existsSync(statusFile)) { + if (Object.keys(properties).length === 0) { + logger.debug("No known repository properties were found."); + } else { logger.debug( - "Overlay status cache entry found but status file is missing." + "Loaded the following values for the repository properties:" ); - return void 0; + for (const [property, value] of Object.entries(properties).sort( + ([nameA], [nameB]) => nameA.localeCompare(nameB) + )) { + logger.debug(` ${property}: ${value}`); + } } - const contents = await fs7.promises.readFile(statusFile, "utf-8"); - const parsed = JSON.parse(contents); - if (!isObject(parsed) || typeof parsed["attemptedToBuildOverlayBaseDatabase"] !== "boolean" || typeof parsed["builtOverlayBaseDatabase"] !== "boolean") { - logger.debug( - "Ignoring overlay status cache entry with unexpected format." + if (unrecognisedProperties.length > 0) { + const unrecognisedPropertyList = unrecognisedProperties.map((name) => `'${name}'`).join(", "); + logger.warning( + `Found repository properties (${unrecognisedPropertyList}), which look like CodeQL Action repository properties, but which are not understood by this version of the CodeQL Action. Do you need to update to a newer version?` ); - return void 0; } - return parsed; - } catch (error3) { - logger.warning( - `Failed to restore overlay status from cache: ${getErrorMessage(error3)}` + return properties; + } catch (e) { + throw new Error( + `Encountered an error while trying to determine repository properties: ${e}` ); - return void 0; } } -async function saveOverlayStatus(codeql, languages, diskUsage, status, logger) { - const cacheKey3 = await getCacheKey(codeql, languages, diskUsage); - const statusFile = getStatusFilePath(languages); - try { - await fs7.promises.mkdir(path8.dirname(statusFile), { recursive: true }); - await fs7.promises.writeFile(statusFile, JSON.stringify(status)); - const cacheId = await waitForResultWithTimeLimit( - MAX_CACHE_OPERATION_MS, - actionsCache.saveCache([statusFile], cacheKey3), - () => { - logger.warning("Timed out saving overlay status to cache."); - } +function setProperty(properties, name, value, logger) { + const propertyOptions = repositoryPropertyParsers[name]; + if (propertyOptions.validate(value)) { + properties[name] = propertyOptions.parse(name, value, logger); + } else { + throw new Error( + `Unexpected value for repository property '${name}' (${typeof value}), got: ${JSON.stringify(value)}` ); - if (cacheId === void 0) { - return false; - } - logger.debug(`Saved overlay status to Actions cache with key ${cacheKey3}`); - return true; - } catch (error3) { + } +} +function parseBooleanRepositoryProperty(name, value, logger) { + if (value !== "true" && value !== "false") { logger.warning( - `Failed to save overlay status to cache: ${getErrorMessage(error3)}` + `Repository property '${name}' has unexpected value '${value}'. Expected 'true' or 'false'. Defaulting to false.` + ); + } + return value === "true"; +} +function parseStringRepositoryProperty(_name, value) { + return value; +} +var KNOWN_REPOSITORY_PROPERTY_NAMES = new Set( + Object.values(RepositoryPropertyName) +); +function isKnownPropertyName(name) { + return KNOWN_REPOSITORY_PROPERTY_NAMES.has(name); +} + +// src/config/db-config.ts +function shouldCombine(inputValue) { + return !!inputValue?.trim().startsWith("+"); +} +var PACK_IDENTIFIER_PATTERN = (function() { + const alphaNumeric = "[a-z0-9]"; + const alphaNumericDash = "[a-z0-9-]"; + const component = `${alphaNumeric}(${alphaNumericDash}*${alphaNumeric})?`; + return new RegExp(`^${component}/${component}$`); +})(); +function parsePacksSpecification(packStr) { + if (typeof packStr !== "string") { + throw new ConfigurationError(getPacksStrInvalid(packStr)); + } + packStr = packStr.trim(); + const atIndex = packStr.indexOf("@"); + const colonIndex = packStr.indexOf(":", atIndex); + const packStart = 0; + const versionStart = atIndex + 1 || void 0; + const pathStart = colonIndex + 1 || void 0; + const packEnd = Math.min( + atIndex > 0 ? atIndex : Infinity, + colonIndex > 0 ? colonIndex : Infinity, + packStr.length + ); + const versionEnd = versionStart ? Math.min(colonIndex > 0 ? colonIndex : Infinity, packStr.length) : void 0; + const pathEnd = pathStart ? packStr.length : void 0; + const packName = packStr.slice(packStart, packEnd).trim(); + const version = versionStart ? packStr.slice(versionStart, versionEnd).trim() : void 0; + const packPath = pathStart ? packStr.slice(pathStart, pathEnd).trim() : void 0; + if (!PACK_IDENTIFIER_PATTERN.test(packName)) { + throw new ConfigurationError(getPacksStrInvalid(packStr)); + } + if (version) { + try { + new semver5.Range(version); + } catch { + throw new ConfigurationError(getPacksStrInvalid(packStr)); + } + } + if (packPath && (path6.isAbsolute(packPath) || // Permit using "/" instead of "\" on Windows + // Use `x.split(y).join(z)` as a polyfill for `x.replaceAll(y, z)` since + // if we used a regex we'd need to escape the path separator on Windows + // which seems more awkward. + path6.normalize(packPath).split(path6.sep).join("/") !== packPath.split(path6.sep).join("/"))) { + throw new ConfigurationError(getPacksStrInvalid(packStr)); + } + if (!packPath && pathStart) { + throw new ConfigurationError(getPacksStrInvalid(packStr)); + } + return { + name: packName, + version, + path: packPath + }; +} +function validatePackSpecification(pack) { + return prettyPrintPack(parsePacksSpecification(pack)); +} +function parsePacksFromInput(rawPacksInput, languages, packsInputCombines) { + if (!rawPacksInput?.trim()) { + return void 0; + } + if (languages.length > 1) { + throw new ConfigurationError( + "Cannot specify a 'packs' input in a multi-language analysis. Use a codeql-config.yml file instead and specify packs by language." + ); + } else if (languages.length === 0) { + throw new ConfigurationError( + "No languages specified. Cannot process the packs input." ); - return false; } + rawPacksInput = rawPacksInput.trim(); + if (packsInputCombines) { + rawPacksInput = rawPacksInput.trim().substring(1).trim(); + if (!rawPacksInput) { + throw new ConfigurationError( + getConfigFilePropertyError( + void 0, + "packs", + "A '+' was used in the 'packs' input to specify that you wished to add some packs to your CodeQL analysis. However, no packs were specified. Please either remove the '+' or specify some packs." + ) + ); + } + } + return { + [languages[0]]: rawPacksInput.split(",").reduce((packs, pack) => { + packs.push(validatePackSpecification(pack)); + return packs; + }, []) + }; } -async function getCacheKey(codeql, languages, diskUsage) { - const diskSpaceToNearest10Gb = `${10 * Math.floor(diskUsage.numTotalBytes / (10 * 1024 * 1024 * 1024))}GB`; - return `codeql-overlay-status-${[...languages].sort().join("+")}-${(await codeql.getVersion()).version}-runner-${diskSpaceToNearest10Gb}`; -} - -// src/trap-caching.ts -var fs8 = __toESM(require("fs")); -var path9 = __toESM(require("path")); -var actionsCache2 = __toESM(require_cache4()); -var CACHE_VERSION = 1; -var CODEQL_TRAP_CACHE_PREFIX = "codeql-trap"; -var MINIMUM_CACHE_MB_TO_UPLOAD = 10; -var MAX_CACHE_OPERATION_MS2 = 12e4; -async function downloadTrapCaches(codeql, languages, logger) { - const result = {}; - const languagesSupportingCaching = await getLanguagesSupportingCaching( - codeql, +async function calculateAugmentation(rawPacksInput, rawQueriesInput, repositoryProperties, languages) { + const packsInputCombines = shouldCombine(rawPacksInput); + const packsInput = parsePacksFromInput( + rawPacksInput, languages, - logger - ); - logger.info( - `Found ${languagesSupportingCaching.length} languages that support TRAP caching` + packsInputCombines ); - if (languagesSupportingCaching.length === 0) return result; - const cachesDir = path9.join( - getTemporaryDirectory(), - "trapCaches" + const queriesInputCombines = shouldCombine(rawQueriesInput); + const queriesInput = parseQueriesFromInput( + rawQueriesInput, + queriesInputCombines ); - for (const language of languagesSupportingCaching) { - const cacheDir2 = path9.join(cachesDir, language); - fs8.mkdirSync(cacheDir2, { recursive: true }); - result[language] = cacheDir2; + const repoExtraQueries = repositoryProperties["github-codeql-extra-queries" /* EXTRA_QUERIES */]; + const repoExtraQueriesCombines = shouldCombine(repoExtraQueries); + const repoPropertyQueries = { + combines: repoExtraQueriesCombines, + input: parseQueriesFromInput( + repoExtraQueries, + repoExtraQueriesCombines, + new ConfigurationError( + getRepoPropertyError( + "github-codeql-extra-queries" /* EXTRA_QUERIES */, + getEmptyCombinesError() + ) + ) + ) + }; + return { + packsInputCombines, + packsInput: packsInput?.[languages[0]], + queriesInput, + queriesInputCombines, + repoPropertyQueries + }; +} +function parseQueriesFromInput(rawQueriesInput, queriesInputCombines, errorToThrow) { + if (!rawQueriesInput) { + return void 0; } - if (await isAnalyzingDefaultBranch()) { - logger.info( - "Analyzing default branch. Skipping downloading of TRAP caches." + 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, + "queries", + "A '+' was used in the 'queries' input to specify that you wished to add some packs to your CodeQL analysis. However, no packs were specified. Please either remove the '+' or specify some packs." + ) ); - return result; - } - let baseSha = "unknown"; - const eventPath = process.env.GITHUB_EVENT_PATH; - if (getWorkflowEventName() === "pull_request" && eventPath !== void 0) { - const event = JSON.parse(fs8.readFileSync(path9.resolve(eventPath), "utf-8")); - baseSha = event.pull_request?.base?.sha || baseSha; } - for (const language of languages) { - const cacheDir2 = result[language]; - if (cacheDir2 === void 0) continue; - const preferredKey = await cacheKey(codeql, language, baseSha); + return trimmedInput.split(",").map((query) => ({ uses: query.trim() })); +} +function combineQueries(logger, config, augmentationProperties) { + const result = []; + if (augmentationProperties.repoPropertyQueries?.input) { logger.info( - `Looking in Actions cache for TRAP cache with key ${preferredKey}` - ); - const found = await waitForResultWithTimeLimit( - MAX_CACHE_OPERATION_MS2, - actionsCache2.restoreCache([cacheDir2], preferredKey, [ - // Fall back to any cache with the right key prefix - await cachePrefix(codeql, language) - ]), - () => { - logger.info( - `Timed out downloading cache for ${language}, will continue without it` - ); - } + `Found query configuration in the repository properties (${"github-codeql-extra-queries" /* EXTRA_QUERIES */}): ${augmentationProperties.repoPropertyQueries.input.map((q) => q.uses).join(", ")}` ); - if (found === void 0) { - logger.info(`No TRAP cache found in Actions cache for ${language}`); - delete result[language]; - } - } - return result; -} -async function uploadTrapCaches(codeql, config, logger) { - if (!await isAnalyzingDefaultBranch()) return false; - for (const language of config.languages) { - const cacheDir2 = config.trapCaches[language]; - if (cacheDir2 === void 0) continue; - const trapFolderSize = await tryGetFolderBytes(cacheDir2, logger); - if (trapFolderSize === void 0) { + if (!augmentationProperties.repoPropertyQueries.combines) { logger.info( - `Skipping upload of TRAP cache for ${language} as we couldn't determine its size` + `The queries configured in the repository properties don't allow combining with other query settings. Any queries configured elsewhere will be ignored.` ); - continue; + return augmentationProperties.repoPropertyQueries.input; + } else { + result.push(...augmentationProperties.repoPropertyQueries.input); } - if (trapFolderSize < MINIMUM_CACHE_MB_TO_UPLOAD * 1048576) { - logger.info( - `Skipping upload of TRAP cache for ${language} as it is too small` - ); - continue; + } + if (augmentationProperties.queriesInput) { + if (!augmentationProperties.queriesInputCombines) { + return result.concat(augmentationProperties.queriesInput); + } else { + result.push(...augmentationProperties.queriesInput); } - const key = await cacheKey( - codeql, - language, - process.env.GITHUB_SHA || "unknown" - ); - logger.info(`Uploading TRAP cache to Actions cache with key ${key}`); - await waitForResultWithTimeLimit( - MAX_CACHE_OPERATION_MS2, - actionsCache2.saveCache([cacheDir2], key), - () => { - logger.info( - `Timed out waiting for TRAP cache for ${language} to upload, will continue without uploading` - ); - } - ); } - return true; -} -async function cleanupTrapCaches(config, features, logger) { - if (!await features.getValue("cleanup_trap_caches" /* CleanupTrapCaches */)) { - return { - trap_cache_cleanup_skipped_because: "feature disabled" - }; + if (config.queries) { + result.push(...config.queries); } - logger.warning( - "TRAP cache cleanup is deprecated and will be removed in May 2026. We recommend instead disabling TRAP caching by passing the `trap-caching: false` input to the `init` Action." + return result; +} +function generateCodeScanningConfig(logger, originalUserInput, augmentationProperties) { + const augmentedConfig = cloneObject(originalUserInput); + augmentedConfig.queries = combineQueries( + logger, + augmentedConfig, + augmentationProperties ); - if (!await isAnalyzingDefaultBranch()) { - return { - trap_cache_cleanup_skipped_because: "not analyzing default branch" - }; + logger.debug( + `Combined queries: ${augmentedConfig.queries?.map((q) => q.uses).join(",")}` + ); + if (augmentedConfig.queries?.length === 0) { + delete augmentedConfig.queries; + } + if (augmentationProperties.packsInput) { + if (augmentationProperties.packsInputCombines) { + if (Array.isArray(augmentedConfig.packs)) { + augmentedConfig.packs = (augmentedConfig.packs || []).concat( + augmentationProperties.packsInput + ); + } else if (!augmentedConfig.packs) { + augmentedConfig.packs = augmentationProperties.packsInput; + } else { + const language = Object.keys(augmentedConfig.packs)[0]; + augmentedConfig.packs[language] = augmentedConfig.packs[language].concat(augmentationProperties.packsInput); + } + } else { + augmentedConfig.packs = augmentationProperties.packsInput; + } + } + if (Array.isArray(augmentedConfig.packs) && !augmentedConfig.packs.length) { + delete augmentedConfig.packs; } + return augmentedConfig; +} +function parseUserConfig(logger, pathInput, contents, validateConfig) { try { - let totalBytesCleanedUp = 0; - const allCaches = await listActionsCaches( - CODEQL_TRAP_CACHE_PREFIX, - await getRef() + const schema = ( + // eslint-disable-next-line @typescript-eslint/no-require-imports + require_db_config_schema() ); - for (const language of config.languages) { - if (config.trapCaches[language]) { - const cachesToRemove = await getTrapCachesForLanguage( - allCaches, - language, - logger - ); - cachesToRemove.sort((a, b) => a.created_at.localeCompare(b.created_at)); - const mostRecentCache = cachesToRemove.pop(); - logger.debug( - `Keeping most recent TRAP cache (${JSON.stringify(mostRecentCache)})` - ); - if (cachesToRemove.length === 0) { - logger.info(`No TRAP caches to clean up for ${language}.`); - continue; - } - for (const cache of cachesToRemove) { - logger.debug(`Cleaning up TRAP cache (${JSON.stringify(cache)})`); - await deleteActionsCache(cache.id); + const doc = load(contents); + if (validateConfig) { + const result = new jsonschema.Validator().validate(doc, schema); + if (result.errors.length > 0) { + for (const error3 of result.errors) { + logger.error(error3.stack); } - const bytesCleanedUp = cachesToRemove.reduce( - (acc, item) => acc + item.size_in_bytes, - 0 - ); - totalBytesCleanedUp += bytesCleanedUp; - const megabytesCleanedUp = (bytesCleanedUp / (1024 * 1024)).toFixed(2); - logger.info( - `Cleaned up ${megabytesCleanedUp} MiB of old TRAP caches for ${language}.` + throw new ConfigurationError( + getInvalidConfigFileMessage( + pathInput, + result.errors.map((e) => e.stack) + ) ); } } - return { trap_cache_cleanup_size_bytes: totalBytesCleanedUp }; - } catch (e) { - if (asHTTPError(e)?.status === 403) { - logger.warning( - `Could not cleanup TRAP caches as the token did not have the required permissions. To clean up TRAP caches, ensure the token has the "actions:write" permission. See ${"https://docs.github.com/en/actions/using-jobs/assigning-permissions-to-jobs" /* ASSIGNING_PERMISSIONS_TO_JOBS */} for more information.` + return doc; + } catch (error3) { + if (error3 instanceof YAMLException) { + throw new ConfigurationError( + getConfigFileParseErrorMessage(pathInput, error3.message) ); - } else { - logger.info(`Failed to cleanup TRAP caches, continuing. Details: ${e}`); } - return { trap_cache_cleanup_error: getErrorMessage(e) }; + throw error3; } } -async function getTrapCachesForLanguage(allCaches, language, logger) { - logger.debug(`Listing TRAP caches for ${language}`); - for (const cache of allCaches) { - if (!cache.created_at || !cache.id || !cache.key || !cache.size_in_bytes) { - throw new Error( - `An unexpected cache item was returned from the API that was missing one or more required fields: ${JSON.stringify(cache)}` - ); - } + +// src/config/remote-file.ts +var DEFAULT_CONFIG_FILE_NAME = ".github/codeql-action.yaml"; +var DEFAULT_CONFIG_FILE_REF = "main"; +function getDefaultOwner(env) { + const currentRepoNwo = env.getRequired("GITHUB_REPOSITORY" /* GITHUB_REPOSITORY */); + const nwoParts = currentRepoNwo.split("/"); + if (nwoParts.length !== 2 || nwoParts[0].trim().length === 0) { + throw new Error( + `Expected ${"GITHUB_REPOSITORY" /* GITHUB_REPOSITORY */} to contain a name with owner, but got '${currentRepoNwo}'.` + ); } - return allCaches.filter((cache) => { - return cache.key?.includes(`-${language}-`); + return nwoParts[0].trim(); +} +var OLD_REMOTE_ADDRESS_FORMAT = new RegExp( + "(?[^/]+)/(?[^/]+)/(?[^@]+)@(?.*)" +); +function parseOldRemoteFileAddress(input) { + const pieces = OLD_REMOTE_ADDRESS_FORMAT.exec(input); + if (pieces?.groups === void 0 || pieces.length < 5) { + return new Failure(void 0); + } + return new Success({ + owner: pieces.groups.owner.trim(), + repo: pieces.groups.repo.trim(), + path: pieces.groups.path.trim(), + ref: pieces.groups.ref.trim() }); } -async function getLanguagesSupportingCaching(codeql, languages, logger) { - const result = []; - const resolveResult = await codeql.resolveLanguages(); - outer: for (const lang of languages) { - const extractorsForLanguage = resolveResult.extractors[lang]; - if (extractorsForLanguage === void 0) { - logger.info( - `${lang} does not support TRAP caching (couldn't find an extractor)` - ); - continue; - } - if (extractorsForLanguage.length !== 1) { +async function parseRemoteFileAddress(actionState, configFile) { + const oldFormatAddressResult = parseOldRemoteFileAddress(configFile); + if (oldFormatAddressResult.isSuccess()) { + return oldFormatAddressResult.value; + } + const allowNewFormat = await actionState.features.getValue( + "new_remote_file_addresses" /* NewRemoteFileAddresses */ + ); + if (!allowNewFormat) { + throw new ConfigurationError( + getConfigFileRepoOldFormatInvalidMessage(configFile) + ); + } + const format = new RegExp( + "^((?[^:@/]+)/)?(?[^:@/]+)(@(?[^:]+))?(:(?.+))?$" + ); + const pieces = format.exec(configFile.trim()); + const repo = pieces?.groups?.repo?.trim(); + if (!pieces?.groups || !repo || repo.length === 0) { + throw new ConfigurationError( + getConfigFileRepoFormatInvalidMessage(configFile) + ); + } + const owner = pieces.groups.owner?.trim(); + const path29 = pieces.groups.path?.trim(); + const ref = pieces.groups.ref?.trim(); + if (path29?.startsWith("/")) { + throw new ConfigurationError( + `The path component of '${configFile}' cannot be an absolute path.` + ); + } + return { + owner: owner || getDefaultOwner(actionState.env), + repo, + path: path29 || DEFAULT_CONFIG_FILE_NAME, + ref: ref || DEFAULT_CONFIG_FILE_REF + }; +} + +// src/config/file.ts +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( - `${lang} does not support TRAP caching (found multiple extractors)` + `Using configuration file input from repository property: ${propertyValue}` ); - continue; - } - const extractor = extractorsForLanguage[0]; - const trapCacheOptions = extractor.extractor_options?.trap?.properties?.cache?.properties; - if (trapCacheOptions === void 0) { + return propertyValue; + } else { logger.info( - `${lang} does not support TRAP caching (missing option group)` + "Ignoring configuration file input from repository property, because the corresponding feature flag is disabled." ); - continue; - } - for (const requiredOpt of ["dir", "bound", "write"]) { - if (!(requiredOpt in trapCacheOptions)) { - logger.info( - `${lang} does not support TRAP caching (missing ${requiredOpt} option)` - ); - continue outer; - } } - result.push(lang); } - return result; -} -async function cacheKey(codeql, language, baseSha) { - return `${await cachePrefix(codeql, language)}${baseSha}`; + return void 0; } -async function cachePrefix(codeql, language) { - return `${CODEQL_TRAP_CACHE_PREFIX}-${CACHE_VERSION}-${(await codeql.getVersion()).version}-${language}-`; +async function getRemoteConfig(actionState, configFile, apiDetails) { + const address = await parseRemoteFileAddress(actionState, configFile); + const response = await getApiClientWithExternalAuth(apiDetails).rest.repos.getContent({ + owner: address.owner, + repo: address.repo, + path: address.path, + ref: address.ref + }); + let fileContents; + if ("content" in response.data && response.data.content !== void 0) { + fileContents = response.data.content; + } else if (Array.isArray(response.data)) { + throw new ConfigurationError( + getConfigFileDirectoryGivenMessage(configFile) + ); + } else { + throw new ConfigurationError( + getConfigFileFormatInvalidMessage(configFile) + ); + } + const validateConfig = await actionState.features.getValue( + "validate_db_config" /* ValidateDbConfig */ + ); + return parseUserConfig( + actionState.logger, + configFile, + Buffer.from(fileContents, "base64").toString("binary"), + validateConfig + ); } -// src/config-utils.ts -var OVERLAY_MINIMUM_AVAILABLE_DISK_SPACE_MB = 2e4; -var OVERLAY_MINIMUM_AVAILABLE_DISK_SPACE_BYTES = OVERLAY_MINIMUM_AVAILABLE_DISK_SPACE_MB * 1e6; -var OVERLAY_MINIMUM_AVAILABLE_DISK_SPACE_V2_MB = 14e3; -var OVERLAY_MINIMUM_AVAILABLE_DISK_SPACE_V2_BYTES = OVERLAY_MINIMUM_AVAILABLE_DISK_SPACE_V2_MB * 1e6; -var OVERLAY_MINIMUM_MEMORY_MB = 5 * 1024; -var CODEQL_VERSION_REDUCED_OVERLAY_MEMORY_USAGE = "2.24.3"; -async function getSupportedLanguageMap(codeql, logger) { - const resolveSupportedLanguagesUsingCli = await codeql.supportsFeature( - "builtinExtractorsSpecifyDefaultQueries" /* BuiltinExtractorsSpecifyDefaultQueries */ - ); - const resolveResult = await codeql.resolveLanguages({ - filterToLanguagesWithQueries: resolveSupportedLanguagesUsingCli - }); - if (resolveSupportedLanguagesUsingCli) { +// src/diagnostics.ts +var import_fs = require("fs"); +var import_path = __toESM(require("path")); +var unwrittenDiagnostics = []; +var unwrittenDefaultLanguageDiagnostics = []; +var diagnosticCounter = 0; +function makeDiagnostic(id, name, data = void 0) { + return { + ...data, + timestamp: data?.timestamp ?? (/* @__PURE__ */ new Date()).toISOString(), + source: { ...data?.source, id, name } + }; +} +function addDiagnostic(config, language, diagnostic) { + const logger = getActionsLogger(); + const databasePath = language ? getCodeQLDatabasePath(config, language) : config.dbLocation; + if ((0, import_fs.existsSync)(databasePath)) { + writeDiagnostic(config, language, diagnostic); + } else { logger.debug( - `The CodeQL CLI supports the following languages: ${Object.keys(resolveResult.extractors).join(", ")}` + `Writing a diagnostic for ${language}, but the database at ${databasePath} does not exist yet.` ); + unwrittenDiagnostics.push({ diagnostic, language }); } - const supportedLanguages = {}; - for (const extractor of Object.keys(resolveResult.extractors)) { - if (resolveSupportedLanguagesUsingCli || BuiltInLanguage[extractor] !== void 0) { - supportedLanguages[extractor] = extractor; - } - } - if (resolveResult.aliases) { - for (const [alias, extractor] of Object.entries(resolveResult.aliases)) { - supportedLanguages[alias] = extractor; - } - } - return supportedLanguages; } -var baseWorkflowsPath = ".github/workflows"; -function hasActionsWorkflows(sourceRoot) { - const workflowsPath = path10.resolve(sourceRoot, baseWorkflowsPath); - const stats = fs9.lstatSync(workflowsPath, { throwIfNoEntry: false }); - return stats !== void 0 && stats.isDirectory() && fs9.readdirSync(workflowsPath).length > 0; -} -async function getRawLanguagesInRepo(repository, sourceRoot, logger) { - logger.debug( - `Automatically detecting languages (${repository.owner}/${repository.repo})` - ); - const response = await getApiClient().rest.repos.listLanguages({ - owner: repository.owner, - repo: repository.repo - }); - logger.debug(`Languages API response: ${JSON.stringify(response)}`); - const result = Object.keys(response.data).map( - (language) => language.trim().toLowerCase() - ); - if (hasActionsWorkflows(sourceRoot)) { - logger.debug(`Found a .github/workflows directory`); - result.push("actions"); +function addNoLanguageDiagnostic(config, diagnostic) { + if (config !== void 0) { + addDiagnostic( + config, + // Arbitrarily choose the first language. We could also choose all languages, but that + // increases the risk of misinterpreting the data. + config.languages[0], + diagnostic + ); + } else { + unwrittenDefaultLanguageDiagnostics.push(diagnostic); } - logger.debug(`Raw languages in repository: ${result.join(", ")}`); - return result; } -async function getLanguages(codeql, languagesInput, repository, sourceRoot, logger) { - const { rawLanguages, autodetected } = await getRawLanguages( - languagesInput, - repository, - sourceRoot, - logger +function writeDiagnostic(config, language, diagnostic) { + const logger = getActionsLogger(); + const databasePath = language ? getCodeQLDatabasePath(config, language) : config.dbLocation; + const diagnosticsPath = import_path.default.resolve( + databasePath, + "diagnostic", + "codeql-action" ); - const languageMap = await getSupportedLanguageMap(codeql, logger); - const languagesSet = /* @__PURE__ */ new Set(); - const unknownLanguages = []; - for (const language of rawLanguages) { - const extractorName = languageMap[language]; - if (extractorName === void 0) { - unknownLanguages.push(language); - } else { - languagesSet.add(extractorName); - } + try { + (0, import_fs.mkdirSync)(diagnosticsPath, { recursive: true }); + const uniqueSuffix = (diagnosticCounter++).toString(); + const sanitizedTimestamp = diagnostic.timestamp.replace( + /[^a-zA-Z0-9.-]/g, + "" + ); + const jsonPath = import_path.default.resolve( + diagnosticsPath, + `codeql-action-${sanitizedTimestamp}-${uniqueSuffix}.json` + ); + (0, import_fs.writeFileSync)(jsonPath, JSON.stringify(diagnostic)); + } catch (err) { + logger.warning(`Unable to write diagnostic message to database: ${err}`); + logger.debug(JSON.stringify(diagnostic)); } - const languages = Array.from(languagesSet); - if (!autodetected && unknownLanguages.length > 0) { - throw new ConfigurationError( - getUnknownLanguagesError(unknownLanguages) +} +function logUnwrittenDiagnostics() { + const logger = getActionsLogger(); + const num = unwrittenDiagnostics.length; + if (num > 0) { + logger.warning( + `${num} diagnostic(s) could not be written to the database and will not appear on the Tool Status Page.` ); + for (const unwritten of unwrittenDiagnostics) { + logger.debug(JSON.stringify(unwritten.diagnostic)); + } } - if (languages.length === 0) { - throw new ConfigurationError(getNoLanguagesError()); +} +function flushDiagnostics(config) { + const logger = getActionsLogger(); + const diagnosticsCount = unwrittenDiagnostics.length + unwrittenDefaultLanguageDiagnostics.length; + logger.debug(`Writing ${diagnosticsCount} diagnostic(s) to database.`); + for (const unwritten of unwrittenDiagnostics) { + writeDiagnostic(config, unwritten.language, unwritten.diagnostic); } - if (autodetected) { - logger.info(`Autodetected languages: ${languages.join(", ")}`); - } else { - logger.info(`Languages from configuration: ${languages.join(", ")}`); + for (const unwritten of unwrittenDefaultLanguageDiagnostics) { + addNoLanguageDiagnostic(config, unwritten); } - return languages; + unwrittenDiagnostics = []; + unwrittenDefaultLanguageDiagnostics = []; } -function getRawLanguagesNoAutodetect(languagesInput) { - return (languagesInput || "").split(",").map((x) => x.trim().toLowerCase()).filter((x) => x.length > 0); +function makeTelemetryDiagnostic(id, name, attributes) { + return makeDiagnostic(id, name, { + attributes, + visibility: { + cliSummaryTable: false, + statusPage: false, + telemetry: true + } + }); } -async function getRawLanguages(languagesInput, repository, sourceRoot, logger) { - const languagesFromInput = getRawLanguagesNoAutodetect(languagesInput); - if (languagesFromInput.length > 0) { - return { rawLanguages: languagesFromInput, autodetected: false }; + +// src/diff-informed-analysis-utils.ts +var fs6 = __toESM(require("fs")); +async function getDiffInformedAnalysisBranches(codeql, features, logger) { + if (!await features.getValue("diff_informed_queries" /* DiffInformedQueries */, codeql)) { + return void 0; } - return { - rawLanguages: await getRawLanguagesInRepo(repository, sourceRoot, logger), - autodetected: true - }; -} -async function initActionState({ - languagesInput, - queriesInput, - packsInput, - buildModeInput, - dbLocation, - dependencyCachingEnabled, - debugMode, - debugArtifactName, - debugDatabaseName, - repository, - tempDir, - codeql, - sourceRoot, - githubVersion, - features, - repositoryProperties, - analysisKinds, - logger, - enableFileCoverageInformation -}, userConfig) { - const languages = await getLanguages( - codeql, - languagesInput, - repository, - sourceRoot, - logger - ); - const buildMode = await parseBuildModeInput( - buildModeInput, - languages, - features, - logger - ); - const augmentationProperties = await calculateAugmentation( - packsInput, - queriesInput, - repositoryProperties, - languages - ); - if (analysisKinds.length === 1 && analysisKinds.includes("code-quality" /* CodeQuality */) && augmentationProperties.repoPropertyQueries.input) { + const gitHubVersion = await getGitHubVersion(); + if (gitHubVersion.type === "GitHub Enterprise Server" /* GHES */ && satisfiesGHESVersion(gitHubVersion.version, "<3.19", true)) { + return void 0; + } + const branches = getPullRequestBranches(); + if (!branches) { logger.info( - `Ignoring queries configured in the repository properties, because query customisations are not supported for Code Quality analyses.` + "Not performing diff-informed analysis because we are not analyzing a pull request." ); - augmentationProperties.repoPropertyQueries = { - combines: false, - input: void 0 - }; } - const computedConfig = generateCodeScanningConfig( - logger, - userConfig, - augmentationProperties - ); - return { - version: getActionVersion(), - analysisKinds, - languages, - buildMode, - originalUserInput: userConfig, - computedConfig, - tempDir, - codeQLCmd: codeql.getPath(), - gitHubVersion: githubVersion, - dbLocation: dbLocationOrDefault(dbLocation, tempDir), - debugMode, - debugArtifactName, - debugDatabaseName, - trapCaches: {}, - trapCacheDownloadTime: 0, - dependencyCachingEnabled: getCachingKind(dependencyCachingEnabled), - dependencyCachingRestoredKeys: [], - extraQueryExclusions: [], - overlayDatabaseMode: "none" /* None */, - useOverlayDatabaseCaching: false, - overlayModeSetExplicitly: false, - repositoryProperties, - enableFileCoverageInformation - }; -} -async function downloadCacheWithTime(codeQL, languages, logger) { - const start = import_perf_hooks.performance.now(); - const trapCaches = await downloadTrapCaches(codeQL, languages, logger); - const trapCacheDownloadTime = import_perf_hooks.performance.now() - start; - return { trapCaches, trapCacheDownloadTime }; + return branches; } -async function loadUserConfig(logger, configFile, workspacePath, apiDetails, tempDir, validateConfig) { - if (isLocal(configFile)) { - if (configFile !== userConfigFromActionPath(tempDir)) { - configFile = path10.resolve(workspacePath, configFile); - if (!(configFile + path10.sep).startsWith(workspacePath + path10.sep)) { - throw new ConfigurationError( - getConfigFileOutsideWorkspaceErrorMessage(configFile) - ); - } - } - return getLocalConfig(logger, configFile, validateConfig); - } else { - return await getRemoteConfig( - logger, - configFile, - apiDetails, - validateConfig +async function prepareDiffInformedAnalysis(codeql, features, logger) { + let branches; + try { + branches = await getDiffInformedAnalysisBranches(codeql, features, logger); + } catch (e) { + logger.warning( + `Failed to determine branch information for diff-informed analysis: ${getErrorMessage(e)}` ); + return false; } -} -var OVERLAY_ANALYSIS_FEATURES = { - cpp: "overlay_analysis_cpp" /* OverlayAnalysisCpp */, - csharp: "overlay_analysis_csharp" /* OverlayAnalysisCsharp */, - go: "overlay_analysis_go" /* OverlayAnalysisGo */, - java: "overlay_analysis_java" /* OverlayAnalysisJava */, - javascript: "overlay_analysis_javascript" /* OverlayAnalysisJavascript */, - python: "overlay_analysis_python" /* OverlayAnalysisPython */, - ruby: "overlay_analysis_ruby" /* OverlayAnalysisRuby */ -}; -var OVERLAY_ANALYSIS_CODE_SCANNING_FEATURES = { - cpp: "overlay_analysis_code_scanning_cpp" /* OverlayAnalysisCodeScanningCpp */, - csharp: "overlay_analysis_code_scanning_csharp" /* OverlayAnalysisCodeScanningCsharp */, - go: "overlay_analysis_code_scanning_go" /* OverlayAnalysisCodeScanningGo */, - java: "overlay_analysis_code_scanning_java" /* OverlayAnalysisCodeScanningJava */, - javascript: "overlay_analysis_code_scanning_javascript" /* OverlayAnalysisCodeScanningJavascript */, - python: "overlay_analysis_code_scanning_python" /* OverlayAnalysisCodeScanningPython */, - ruby: "overlay_analysis_code_scanning_ruby" /* OverlayAnalysisCodeScanningRuby */ -}; -async function checkOverlayAnalysisFeatureEnabled(features, codeql, languages, codeScanningConfig) { - if (!await features.getValue("overlay_analysis" /* OverlayAnalysis */, codeql)) { - return new Failure("overall-feature-not-enabled" /* OverallFeatureNotEnabled */); - } - let enableForCodeScanningOnly = false; - for (const language of languages) { - const feature = OVERLAY_ANALYSIS_FEATURES[language]; - if (feature && await features.getValue(feature, codeql)) { - continue; - } - const codeScanningFeature = OVERLAY_ANALYSIS_CODE_SCANNING_FEATURES[language]; - if (codeScanningFeature && await features.getValue(codeScanningFeature, codeql)) { - enableForCodeScanningOnly = true; - continue; - } - return new Failure("language-not-enabled" /* LanguageNotEnabled */); + if (!branches) { + return false; } - if (enableForCodeScanningOnly) { - const usesDefaultQueriesOnly = codeScanningConfig["disable-default-queries"] !== true && codeScanningConfig.packs === void 0 && codeScanningConfig.queries === void 0 && codeScanningConfig["query-filters"] === void 0; - if (!usesDefaultQueriesOnly) { - return new Failure("non-default-queries" /* NonDefaultQueries */); - } + try { + return await computeAndPersistDiffRanges(branches, logger); + } catch (e) { + logger.warning( + `Failed to compute diff-informed analysis ranges: ${getErrorMessage(e)}` + ); + return false; } - return new Success(void 0); } -function runnerHasSufficientDiskSpace(diskUsage, logger, useV2ResourceChecks) { - const minimumDiskSpaceBytes = useV2ResourceChecks ? OVERLAY_MINIMUM_AVAILABLE_DISK_SPACE_V2_BYTES : OVERLAY_MINIMUM_AVAILABLE_DISK_SPACE_BYTES; - if (diskUsage.numAvailableBytes < minimumDiskSpaceBytes) { - const diskSpaceMb = Math.round(diskUsage.numAvailableBytes / 1e6); - const minimumDiskSpaceMb = Math.round(minimumDiskSpaceBytes / 1e6); - logger.info( - `Setting overlay database mode to ${"none" /* None */} due to insufficient disk space (${diskSpaceMb} MB, needed ${minimumDiskSpaceMb} MB).` +function writeDiffRangesJsonFile(logger, ranges) { + const jsonContents = JSON.stringify(ranges, null, 2); + const jsonFilePath = getDiffRangesJsonFilePath(); + fs6.writeFileSync(jsonFilePath, jsonContents); + logger.debug( + `Wrote pr-diff-range JSON file to ${jsonFilePath}: +${jsonContents}` + ); +} +function readDiffRangesJsonFile(logger) { + const jsonFilePath = getDiffRangesJsonFilePath(); + if (!fs6.existsSync(jsonFilePath)) { + logger.debug(`Diff ranges JSON file does not exist at ${jsonFilePath}`); + return void 0; + } + const jsonContents = fs6.readFileSync(jsonFilePath, "utf8"); + logger.debug( + `Read pr-diff-range JSON file from ${jsonFilePath}: +${jsonContents}` + ); + try { + return JSON.parse(jsonContents); + } catch (e) { + logger.warning( + `Failed to parse diff ranges JSON file at ${jsonFilePath}: ${e}` ); - return false; + return void 0; } - return true; } -async function runnerHasSufficientMemory(codeql, ramInput, logger) { - if (await codeQlVersionAtLeast( - codeql, - CODEQL_VERSION_REDUCED_OVERLAY_MEMORY_USAGE - )) { - logger.debug( - `Skipping memory check for overlay analysis because CodeQL version is at least ${CODEQL_VERSION_REDUCED_OVERLAY_MEMORY_USAGE}.` - ); - return true; +async function getPullRequestEditedDiffRanges(branches, logger) { + const fileDiffs = await getFileDiffsWithBasehead(branches, logger); + if (fileDiffs === void 0) { + return void 0; } - const memoryFlagValue = getCodeQLMemoryLimit(ramInput, logger); - if (memoryFlagValue < OVERLAY_MINIMUM_MEMORY_MB) { - logger.info( - `Setting overlay database mode to ${"none" /* None */} due to insufficient memory for CodeQL analysis (${memoryFlagValue} MB, needed ${OVERLAY_MINIMUM_MEMORY_MB} MB).` + if (fileDiffs.length >= 300) { + logger.warning( + `Cannot retrieve the full diff because there are too many (${fileDiffs.length}) changed files in the pull request.` ); + return void 0; + } + const results = []; + for (const filediff of fileDiffs) { + const diffRanges = getDiffRanges(filediff, logger); + if (diffRanges === void 0) { + return void 0; + } + results.push(...diffRanges); + } + return results; +} +async function computeAndPersistDiffRanges(branches, logger) { + logger.info("Computing PR diff ranges..."); + const ranges = await getPullRequestEditedDiffRanges(branches, logger); + if (ranges === void 0) { return false; } - logger.debug( - `Memory available for CodeQL analysis is ${memoryFlagValue} MB, which is above the minimum of ${OVERLAY_MINIMUM_MEMORY_MB} MB.` + writeDiffRangesJsonFile(logger, ranges); + const distinctFiles = new Set(ranges.map((r) => r.path)).size; + logger.info( + `Persisted ${ranges.length} diff range(s) across ${distinctFiles} file(s).` ); return true; } -async function checkRunnerResources(codeql, diskUsage, ramInput, logger, useV2ResourceChecks) { - if (!runnerHasSufficientDiskSpace(diskUsage, logger, useV2ResourceChecks)) { - return new Failure("insufficient-disk-space" /* InsufficientDiskSpace */); - } - if (!await runnerHasSufficientMemory(codeql, ramInput, logger)) { - return new Failure("insufficient-memory" /* InsufficientMemory */); - } - return new Success(void 0); -} -async function checkOverlayEnablement(codeql, features, languages, sourceRoot, buildMode, ramInput, codeScanningConfig, repositoryProperties, gitVersion, logger) { - const modeEnv = process.env.CODEQL_OVERLAY_DATABASE_MODE; - if (modeEnv === "overlay" /* Overlay */ || modeEnv === "overlay-base" /* OverlayBase */ || modeEnv === "none" /* None */) { - logger.info( - `Setting overlay database mode to ${modeEnv} from the CODEQL_OVERLAY_DATABASE_MODE environment variable.` +async function getFileDiffsWithBasehead(branches, logger) { + const repositoryNwo = getRepositoryNwoFromEnv( + "CODE_SCANNING_REPOSITORY", + "GITHUB_REPOSITORY" + ); + const basehead = `${branches.base}...${branches.head}`; + try { + const response = await getApiClient().rest.repos.compareCommitsWithBasehead( + { + owner: repositoryNwo.owner, + repo: repositoryNwo.repo, + basehead, + per_page: 1 + } ); - if (modeEnv === "none" /* None */) { - return new Failure("disabled-by-environment-variable" /* DisabledByEnvironmentVariable */); - } - return validateOverlayDatabaseMode( - modeEnv, - false, - true, - codeql, - languages, - sourceRoot, - buildMode, - gitVersion, - logger + logger.debug( + `Response from compareCommitsWithBasehead(${basehead}): +${JSON.stringify(response, null, 2)}` ); + return response.data.files; + } catch (error3) { + if (error3.status) { + logger.warning(`Error retrieving diff ${basehead}: ${error3.message}`); + logger.debug( + `Error running compareCommitsWithBasehead(${basehead}): +Request: ${JSON.stringify(error3.request, null, 2)} +Error Response: ${JSON.stringify(error3.response, null, 2)}` + ); + return void 0; + } else { + throw error3; + } } - if (repositoryProperties["github-codeql-disable-overlay" /* DISABLE_OVERLAY */] === true) { - logger.info( - `Setting overlay database mode to ${"none" /* None */} because the ${"github-codeql-disable-overlay" /* DISABLE_OVERLAY */} repository property is set to true.` - ); - return new Failure("disabled-by-repository-property" /* DisabledByRepositoryProperty */); +} +function getDiffRanges(fileDiff, logger) { + if (fileDiff.patch === void 0) { + if (fileDiff.changes === 0) { + return []; + } + return [ + { + path: fileDiff.filename, + startLine: 0, + endLine: 0 + } + ]; } - const featureResult = await checkOverlayAnalysisFeatureEnabled( - features, - codeql, - languages, - codeScanningConfig - ); - if (featureResult.isFailure()) { - return featureResult; + let currentLine = 0; + let additionRangeStartLine = void 0; + const diffRanges = []; + const diffLines = fileDiff.patch.split("\n"); + diffLines.push(" "); + for (const diffLine of diffLines) { + if (diffLine.startsWith("-")) { + continue; + } + if (diffLine.startsWith("+")) { + if (additionRangeStartLine === void 0) { + additionRangeStartLine = currentLine; + } + currentLine++; + continue; + } + if (additionRangeStartLine !== void 0) { + diffRanges.push({ + path: fileDiff.filename, + startLine: additionRangeStartLine, + endLine: currentLine - 1 + }); + additionRangeStartLine = void 0; + } + if (diffLine.startsWith("@@ ")) { + const match2 = diffLine.match(/^@@ -\d+(?:,\d+)? \+(\d+)(?:,\d+)? @@/); + if (match2 === null) { + logger.warning( + `Cannot parse diff hunk header for ${fileDiff.filename}: ${diffLine}` + ); + return void 0; + } + currentLine = parseInt(match2[1], 10); + continue; + } + if (diffLine.startsWith(" ")) { + currentLine++; + continue; + } } - const performResourceChecks = !await features.getValue( - "overlay_analysis_skip_resource_checks" /* OverlayAnalysisSkipResourceChecks */, - codeql - ); - const useV2ResourceChecks = await features.getValue( - "overlay_analysis_resource_checks_v2" /* OverlayAnalysisResourceChecksV2 */ - ); - const checkOverlayStatus = await features.getValue( - "overlay_analysis_status_check" /* OverlayAnalysisStatusCheck */ - ); - const needDiskUsage = performResourceChecks || checkOverlayStatus; - const diskUsage = needDiskUsage ? await checkDiskUsage(logger) : void 0; - if (needDiskUsage && diskUsage === void 0) { - logger.warning( - `Unable to determine disk usage, therefore setting overlay database mode to ${"none" /* None */}.` - ); - return new Failure("unable-to-determine-disk-usage" /* UnableToDetermineDiskUsage */); + return diffRanges; +} + +// src/languages/builtin.json +var builtin_default = { + languages: [ + "actions", + "cpp", + "csharp", + "go", + "java", + "javascript", + "python", + "ruby", + "rust", + "swift" + ], + aliases: { + c: "cpp", + "c-c++": "cpp", + "c-cpp": "cpp", + "c#": "csharp", + "c++": "cpp", + "java-kotlin": "java", + "javascript-typescript": "javascript", + kotlin: "java", + typescript: "javascript" } - const resourceResult = performResourceChecks && diskUsage !== void 0 ? await checkRunnerResources( - codeql, - diskUsage, - ramInput, - logger, - useV2ResourceChecks - ) : new Success(void 0); - if (resourceResult.isFailure()) { - return resourceResult; +}; + +// src/languages/index.ts +var BuiltInLanguage = /* @__PURE__ */ ((BuiltInLanguage3) => { + BuiltInLanguage3["actions"] = "actions"; + BuiltInLanguage3["cpp"] = "cpp"; + BuiltInLanguage3["csharp"] = "csharp"; + BuiltInLanguage3["go"] = "go"; + BuiltInLanguage3["java"] = "java"; + BuiltInLanguage3["javascript"] = "javascript"; + BuiltInLanguage3["python"] = "python"; + BuiltInLanguage3["ruby"] = "ruby"; + BuiltInLanguage3["rust"] = "rust"; + BuiltInLanguage3["swift"] = "swift"; + return BuiltInLanguage3; +})(BuiltInLanguage || {}); +var builtInLanguageSet = new Set(builtin_default.languages); +function isBuiltInLanguage(language) { + return builtInLanguageSet.has(language); +} +function parseBuiltInLanguage(language) { + language = language.trim().toLowerCase(); + language = builtin_default.aliases[language] ?? language; + if (isBuiltInLanguage(language)) { + return language; } - if (checkOverlayStatus && diskUsage !== void 0 && await shouldSkipOverlayAnalysis(codeql, languages, diskUsage, logger)) { - logger.info( - `Setting overlay database mode to ${"none" /* None */} because overlay analysis previously failed with this combination of languages, disk space, and CodeQL version.` + return void 0; +} + +// src/overlay/diagnostics.ts +async function addOverlayDisablementDiagnostics(config, codeql, overlayDisabledReason) { + addNoLanguageDiagnostic( + config, + makeTelemetryDiagnostic( + "codeql-action/overlay-disabled", + "Overlay analysis disabled", + { + reason: overlayDisabledReason + } + ) + ); + if (overlayDisabledReason === "skipped-due-to-cached-status" /* SkippedDueToCachedStatus */) { + addNoLanguageDiagnostic( + config, + makeDiagnostic( + "codeql-action/overlay-disabled-due-to-cached-status", + "Skipped improved incremental analysis because it failed previously with similar hardware resources", + { + attributes: { + languages: config.languages + }, + markdownMessage: `Improved incremental analysis was skipped because it previously failed for this repository with CodeQL version ${(await codeql.getVersion()).version} on a runner with similar hardware resources. One possible reason for this is that improved incremental analysis can require a significant amount of disk space for some repositories. If you want to try re-enabling improved incremental analysis, increase the disk space available to the runner. If that doesn't help, contact GitHub Support for further assistance. + +Improved incremental analysis will be automatically retried when the next version of CodeQL is released. You can also manually trigger a retry by [removing](${"https://docs.github.com/en/actions/how-tos/manage-workflow-runs/manage-caches#deleting-cache-entries" /* DELETE_ACTIONS_CACHE_ENTRIES */}) \`codeql-overlay-status-*\` entries from the Actions cache.`, + severity: "note", + visibility: { + cliSummaryTable: true, + statusPage: true, + telemetry: false + } + } + ) ); - return new Failure("skipped-due-to-cached-status" /* SkippedDueToCachedStatus */); } - let overlayDatabaseMode; - if (isAnalyzingPullRequest()) { - overlayDatabaseMode = "overlay" /* Overlay */; - logger.info( - `Setting overlay database mode to ${overlayDatabaseMode} with caching because we are analyzing a pull request.` - ); - } else if (await isAnalyzingDefaultBranch()) { - overlayDatabaseMode = "overlay-base" /* OverlayBase */; - logger.info( - `Setting overlay database mode to ${overlayDatabaseMode} with caching because we are analyzing the default branch.` + if (overlayDisabledReason === "disabled-by-repository-property" /* DisabledByRepositoryProperty */) { + addNoLanguageDiagnostic( + config, + makeDiagnostic( + "codeql-action/overlay-disabled-by-repository-property", + "Improved incremental analysis disabled by repository property", + { + attributes: { + languages: config.languages + }, + markdownMessage: `Improved incremental analysis has been disabled because the \`${"github-codeql-disable-overlay" /* DISABLE_OVERLAY */}\` repository property is set to \`true\`. To re-enable improved incremental analysis, set this property to \`false\` or remove it.`, + severity: "note", + visibility: { + cliSummaryTable: true, + statusPage: true, + telemetry: false + } + } + ) ); - } else { - return new Failure("not-pull-request-or-default-branch" /* NotPullRequestOrDefaultBranch */); } - return validateOverlayDatabaseMode( - overlayDatabaseMode, - true, - false, - codeql, - languages, - sourceRoot, - buildMode, - gitVersion, - logger +} + +// src/overlay/status.ts +var fs7 = __toESM(require("fs")); +var path8 = __toESM(require("path")); +var actionsCache = __toESM(require_cache4()); +var MAX_CACHE_OPERATION_MS = 3e4; +var STATUS_FILE_NAME = "overlay-status.json"; +function getStatusFilePath(languages) { + return path8.join( + getTemporaryDirectory(), + "overlay-status", + [...languages].sort().join("+"), + STATUS_FILE_NAME ); } -async function validateOverlayDatabaseMode(overlayDatabaseMode, useOverlayDatabaseCaching, overlayModeSetExplicitly, codeql, languages, sourceRoot, buildMode, gitVersion, logger) { - if (buildMode !== "none" /* None */ && (await Promise.all( - languages.map( - async (l) => l !== "go" /* go */ && // Workaround to allow overlay analysis for Go with any build - // mode, since it does not yet support BMN. The Go autobuilder and/or extractor will - // ensure that overlay-base databases are only created for supported Go build setups, - // and that we'll fall back to full databases in other cases. - await codeql.isTracedLanguage(l) - ) - )).some(Boolean)) { - logger.warning( - `Cannot build an ${overlayDatabaseMode} database because build-mode is set to "${buildMode}" instead of "none". Falling back to creating a normal full database instead.` - ); - return new Failure("incompatible-build-mode" /* IncompatibleBuildMode */); +function createOverlayStatus(attributes, checkRunId) { + const job = { + workflowRunId: getWorkflowRunID(), + workflowRunAttempt: getWorkflowRunAttempt(), + name: getRequiredEnvParam("GITHUB_JOB"), + checkRunId + }; + return { + ...attributes, + job + }; +} +async function shouldSkipOverlayAnalysis(codeql, languages, diskUsage, logger) { + const status = await getOverlayStatus(codeql, languages, diskUsage, logger); + if (status === void 0) { + return false; } - if (!await codeQlVersionAtLeast(codeql, CODEQL_OVERLAY_MINIMUM_VERSION)) { - logger.warning( - `Cannot build an ${overlayDatabaseMode} database because the CodeQL CLI is older than ${CODEQL_OVERLAY_MINIMUM_VERSION}. Falling back to creating a normal full database instead.` + if (status.attemptedToBuildOverlayBaseDatabase && !status.builtOverlayBaseDatabase) { + logger.debug( + "Cached overlay status indicates that building an overlay base database was unsuccessful." ); - return new Failure("incompatible-codeql" /* IncompatibleCodeQl */); + return true; } - const gitRoot = await getGitRoot(sourceRoot); - if (gitRoot === void 0) { - logger.warning( - `Cannot build an ${overlayDatabaseMode} database because the source root "${sourceRoot}" is not inside a git repository. Falling back to creating a normal full database instead.` + logger.debug( + "Cached overlay status does not indicate a previous unsuccessful attempt to build an overlay base database." + ); + return false; +} +async function getOverlayStatus(codeql, languages, diskUsage, logger) { + const cacheKey3 = await getCacheKey(codeql, languages, diskUsage); + const statusFile = getStatusFilePath(languages); + try { + await fs7.promises.mkdir(path8.dirname(statusFile), { recursive: true }); + const foundKey = await waitForResultWithTimeLimit( + MAX_CACHE_OPERATION_MS, + actionsCache.restoreCache([statusFile], cacheKey3), + () => { + logger.warning("Timed out restoring overlay status from cache."); + } ); - return new Failure("no-git-root" /* NoGitRoot */); - } - if (hasSubmodules(gitRoot)) { - if (gitVersion === void 0) { - logger.warning( - `Cannot build an ${overlayDatabaseMode} database because the repository has submodules and the Git version could not be determined. Falling back to creating a normal full database instead.` + if (foundKey === void 0) { + logger.debug("No overlay status found in Actions cache."); + return void 0; + } + if (!fs7.existsSync(statusFile)) { + logger.debug( + "Overlay status cache entry found but status file is missing." ); - return new Failure("incompatible-git" /* IncompatibleGit */); + return void 0; } - if (!gitVersion.isAtLeast(GIT_MINIMUM_VERSION_FOR_OVERLAY_WITH_SUBMODULES)) { - logger.warning( - `Cannot build an ${overlayDatabaseMode} database because the repository has submodules and the installed Git version is older than ${GIT_MINIMUM_VERSION_FOR_OVERLAY_WITH_SUBMODULES}. Falling back to creating a normal full database instead.` + const contents = await fs7.promises.readFile(statusFile, "utf-8"); + const parsed = JSON.parse(contents); + if (!isObject(parsed) || typeof parsed["attemptedToBuildOverlayBaseDatabase"] !== "boolean" || typeof parsed["builtOverlayBaseDatabase"] !== "boolean") { + logger.debug( + "Ignoring overlay status cache entry with unexpected format." ); - return new Failure("incompatible-git" /* IncompatibleGit */); + return void 0; } + return parsed; + } catch (error3) { + logger.warning( + `Failed to restore overlay status from cache: ${getErrorMessage(error3)}` + ); + return void 0; } - return new Success({ - overlayDatabaseMode, - useOverlayDatabaseCaching, - overlayModeSetExplicitly - }); } -async function isTrapCachingEnabled(features, overlayDatabaseMode) { - const trapCaching = getOptionalInput("trap-caching"); - if (trapCaching !== void 0) return trapCaching === "true"; - if (!isHostedRunner()) return false; - if (overlayDatabaseMode !== "none" /* None */ && await features.getValue("overlay_analysis_disable_trap_caching" /* OverlayAnalysisDisableTrapCaching */)) { +async function saveOverlayStatus(codeql, languages, diskUsage, status, logger) { + const cacheKey3 = await getCacheKey(codeql, languages, diskUsage); + const statusFile = getStatusFilePath(languages); + try { + await fs7.promises.mkdir(path8.dirname(statusFile), { recursive: true }); + await fs7.promises.writeFile(statusFile, JSON.stringify(status)); + const cacheId = await waitForResultWithTimeLimit( + MAX_CACHE_OPERATION_MS, + actionsCache.saveCache([statusFile], cacheKey3), + () => { + logger.warning("Timed out saving overlay status to cache."); + } + ); + if (cacheId === void 0) { + return false; + } + logger.debug(`Saved overlay status to Actions cache with key ${cacheKey3}`); + return true; + } catch (error3) { + logger.warning( + `Failed to save overlay status to cache: ${getErrorMessage(error3)}` + ); return false; } - return true; } -async function setCppTrapCachingEnvironmentVariables(config, logger) { - if (config.languages.includes("cpp" /* cpp */)) { - const envVar = "CODEQL_EXTRACTOR_CPP_TRAP_CACHING"; - if (process.env[envVar]) { - logger.info( - `Environment variable ${envVar} already set, leaving it unchanged.` - ); - } else if (config.trapCaches["cpp" /* cpp */]) { - logger.info("Enabling TRAP caching for C/C++."); - core8.exportVariable(envVar, "true"); - } else { - logger.debug(`Disabling TRAP caching for C/C++.`); - core8.exportVariable(envVar, "false"); - } +async function getCacheKey(codeql, languages, diskUsage) { + const diskSpaceToNearest10Gb = `${10 * Math.floor(diskUsage.numTotalBytes / (10 * 1024 * 1024 * 1024))}GB`; + return `codeql-overlay-status-${[...languages].sort().join("+")}-${(await codeql.getVersion()).version}-runner-${diskSpaceToNearest10Gb}`; +} + +// src/trap-caching.ts +var fs8 = __toESM(require("fs")); +var path9 = __toESM(require("path")); +var actionsCache2 = __toESM(require_cache4()); +var CACHE_VERSION = 1; +var CODEQL_TRAP_CACHE_PREFIX = "codeql-trap"; +var MINIMUM_CACHE_MB_TO_UPLOAD = 10; +var MAX_CACHE_OPERATION_MS2 = 12e4; +async function downloadTrapCaches(codeql, languages, logger) { + const result = {}; + const languagesSupportingCaching = await getLanguagesSupportingCaching( + codeql, + languages, + logger + ); + logger.info( + `Found ${languagesSupportingCaching.length} languages that support TRAP caching` + ); + if (languagesSupportingCaching.length === 0) return result; + const cachesDir = path9.join( + getTemporaryDirectory(), + "trapCaches" + ); + for (const language of languagesSupportingCaching) { + const cacheDir2 = path9.join(cachesDir, language); + fs8.mkdirSync(cacheDir2, { recursive: true }); + result[language] = cacheDir2; + } + if (await isAnalyzingDefaultBranch()) { + logger.info( + "Analyzing default branch. Skipping downloading of TRAP caches." + ); + return result; } -} -function dbLocationOrDefault(dbLocation, tempDir) { - return dbLocation || path10.resolve(tempDir, "codeql_databases"); -} -function userConfigFromActionPath(tempDir) { - return path10.resolve(tempDir, "user-config-from-action.yml"); -} -function hasQueryCustomisation(userConfig) { - return isDefined2(userConfig["disable-default-queries"]) || isDefined2(userConfig.queries) || isDefined2(userConfig["query-filters"]); -} -async function applyIncrementalAnalysisSettings(config, hasDiffRanges, codeql, logger) { - if (config.overlayDatabaseMode === "overlay" /* Overlay */ && !hasDiffRanges && !config.overlayModeSetExplicitly) { + let baseSha = "unknown"; + const eventPath = process.env.GITHUB_EVENT_PATH; + if (getWorkflowEventName() === "pull_request" && eventPath !== void 0) { + const event = JSON.parse(fs8.readFileSync(path9.resolve(eventPath), "utf-8")); + baseSha = event.pull_request?.base?.sha || baseSha; + } + for (const language of languages) { + const cacheDir2 = result[language]; + if (cacheDir2 === void 0) continue; + const preferredKey = await cacheKey(codeql, language, baseSha); logger.info( - `Reverting overlay database mode to ${"none" /* None */} because the PR diff ranges could not be computed.` + `Looking in Actions cache for TRAP cache with key ${preferredKey}` ); - config.overlayDatabaseMode = "none" /* None */; - config.useOverlayDatabaseCaching = false; - await addOverlayDisablementDiagnostics( - config, - codeql, - "diff-informed-analysis-not-enabled" /* DiffInformedAnalysisNotEnabled */ + const found = await waitForResultWithTimeLimit( + MAX_CACHE_OPERATION_MS2, + actionsCache2.restoreCache([cacheDir2], preferredKey, [ + // Fall back to any cache with the right key prefix + await cachePrefix(codeql, language) + ]), + () => { + logger.info( + `Timed out downloading cache for ${language}, will continue without it` + ); + } ); + if (found === void 0) { + logger.info(`No TRAP cache found in Actions cache for ${language}`); + delete result[language]; + } } - if (hasDiffRanges) { - config.extraQueryExclusions.push({ - exclude: { tags: "exclude-from-incremental" } - }); - } + return result; } -async function initConfig(features, inputs) { - const { logger, tempDir } = inputs; - if (inputs.configInput) { - if (inputs.configFile) { - logger.warning( - `Both a config file and config input were provided. Ignoring config file.` +async function uploadTrapCaches(codeql, config, logger) { + if (!await isAnalyzingDefaultBranch()) return false; + for (const language of config.languages) { + const cacheDir2 = config.trapCaches[language]; + if (cacheDir2 === void 0) continue; + const trapFolderSize = await tryGetFolderBytes(cacheDir2, logger); + if (trapFolderSize === void 0) { + logger.info( + `Skipping upload of TRAP cache for ${language} as we couldn't determine its size` ); + continue; } - inputs.configFile = userConfigFromActionPath(tempDir); - fs9.writeFileSync(inputs.configFile, inputs.configInput); - logger.debug(`Using config from action input: ${inputs.configFile}`); - } - let userConfig = {}; - if (!inputs.configFile) { - logger.debug("No configuration file was provided"); - } else { - logger.debug(`Using configuration file: ${inputs.configFile}`); - const validateConfig = await features.getValue("validate_db_config" /* ValidateDbConfig */); - userConfig = await loadUserConfig( - logger, - inputs.configFile, - inputs.workspacePath, - inputs.apiDetails, - tempDir, - validateConfig - ); - } - const config = await initActionState(inputs, userConfig); - if (config.analysisKinds.length === 1 && isCodeQualityEnabled(config)) { - if (hasQueryCustomisation(config.computedConfig)) { - throw new ConfigurationError( - "Query customizations are unsupported, because only `code-quality` analysis is enabled." + if (trapFolderSize < MINIMUM_CACHE_MB_TO_UPLOAD * 1048576) { + logger.info( + `Skipping upload of TRAP cache for ${language} as it is too small` ); + continue; } - const queries = codeQualityQueries.map((v) => ({ uses: v })); - config.computedConfig["disable-default-queries"] = true; - config.computedConfig.queries = queries; - config.computedConfig["query-filters"] = []; - } - let gitVersion = void 0; - try { - gitVersion = await getGitVersionOrThrow(); - logger.info(`Using Git version ${gitVersion.fullVersion}`); - await logGitVersionTelemetry(config, gitVersion); - } catch (e) { - logger.warning(`Could not determine Git version: ${getErrorMessage(e)}`); - if (isInTestMode() && process.env["CODEQL_ACTION_TOLERATE_MISSING_GIT_VERSION" /* TOLERATE_MISSING_GIT_VERSION */] !== "true") { - throw e; - } - } - if (await features.getValue("ignore_generated_files" /* IgnoreGeneratedFiles */) && isDynamicWorkflow()) { - try { - const generatedFilesCheckStartedAt = import_perf_hooks.performance.now(); - const generatedFiles = await getGeneratedFiles(inputs.sourceRoot); - const generatedFilesDuration = Math.round( - import_perf_hooks.performance.now() - generatedFilesCheckStartedAt - ); - if (generatedFiles.length > 0) { - config.computedConfig["paths-ignore"] ??= []; - config.computedConfig["paths-ignore"].push(...generatedFiles); + const key = await cacheKey( + codeql, + language, + process.env.GITHUB_SHA || "unknown" + ); + logger.info(`Uploading TRAP cache to Actions cache with key ${key}`); + await waitForResultWithTimeLimit( + MAX_CACHE_OPERATION_MS2, + actionsCache2.saveCache([cacheDir2], key), + () => { logger.info( - `Detected ${generatedFiles.length} generated file(s), which will be excluded from analysis: ${joinAtMost(generatedFiles, ", ", 10)}` + `Timed out waiting for TRAP cache for ${language} to upload, will continue without uploading` ); - } else { - logger.info(`Found no generated files.`); } - await logGeneratedFilesTelemetry( - config, - generatedFilesDuration, - generatedFiles.length - ); - } catch (error3) { - logger.info(`Cannot ignore generated files: ${getErrorMessage(error3)}`); - } - } else { - logger.debug(`Skipping check for generated files.`); - } - const overlayDatabaseModeResult = await checkOverlayEnablement( - inputs.codeql, - inputs.features, - config.languages, - inputs.sourceRoot, - config.buildMode, - inputs.ramInput, - config.computedConfig, - config.repositoryProperties, - gitVersion, - logger - ); - if (overlayDatabaseModeResult.isSuccess()) { - const { - overlayDatabaseMode, - useOverlayDatabaseCaching, - overlayModeSetExplicitly - } = overlayDatabaseModeResult.value; - logger.info( - `Using overlay database mode: ${overlayDatabaseMode} ${useOverlayDatabaseCaching ? "with" : "without"} caching.` - ); - config.overlayDatabaseMode = overlayDatabaseMode; - config.useOverlayDatabaseCaching = useOverlayDatabaseCaching; - config.overlayModeSetExplicitly = overlayModeSetExplicitly; - } else { - const overlayDisabledReason = overlayDatabaseModeResult.value; - logger.info( - `Using overlay database mode: ${"none" /* None */} without caching.` - ); - config.overlayDatabaseMode = "none" /* None */; - config.useOverlayDatabaseCaching = false; - await addOverlayDisablementDiagnostics( - config, - inputs.codeql, - overlayDisabledReason ); } - const hasDiffRanges = await prepareDiffInformedAnalysis( - inputs.codeql, - inputs.features, - logger - ); - await applyIncrementalAnalysisSettings( - config, - hasDiffRanges, - inputs.codeql, - logger + return true; +} +async function cleanupTrapCaches(config, features, logger) { + if (!await features.getValue("cleanup_trap_caches" /* CleanupTrapCaches */)) { + return { + trap_cache_cleanup_skipped_because: "feature disabled" + }; + } + logger.warning( + "TRAP cache cleanup is deprecated and will be removed in May 2026. We recommend instead disabling TRAP caching by passing the `trap-caching: false` input to the `init` Action." ); - if (await isTrapCachingEnabled(features, config.overlayDatabaseMode)) { - const { trapCaches, trapCacheDownloadTime } = await downloadCacheWithTime( - inputs.codeql, - config.languages, - logger + if (!await isAnalyzingDefaultBranch()) { + return { + trap_cache_cleanup_skipped_because: "not analyzing default branch" + }; + } + try { + let totalBytesCleanedUp = 0; + const allCaches = await listActionsCaches( + CODEQL_TRAP_CACHE_PREFIX, + await getRef() ); - config.trapCaches = trapCaches; - config.trapCacheDownloadTime = trapCacheDownloadTime; + for (const language of config.languages) { + if (config.trapCaches[language]) { + const cachesToRemove = await getTrapCachesForLanguage( + allCaches, + language, + logger + ); + cachesToRemove.sort((a, b) => a.created_at.localeCompare(b.created_at)); + const mostRecentCache = cachesToRemove.pop(); + logger.debug( + `Keeping most recent TRAP cache (${JSON.stringify(mostRecentCache)})` + ); + if (cachesToRemove.length === 0) { + logger.info(`No TRAP caches to clean up for ${language}.`); + continue; + } + for (const cache of cachesToRemove) { + logger.debug(`Cleaning up TRAP cache (${JSON.stringify(cache)})`); + await deleteActionsCache(cache.id); + } + const bytesCleanedUp = cachesToRemove.reduce( + (acc, item) => acc + item.size_in_bytes, + 0 + ); + totalBytesCleanedUp += bytesCleanedUp; + const megabytesCleanedUp = (bytesCleanedUp / (1024 * 1024)).toFixed(2); + logger.info( + `Cleaned up ${megabytesCleanedUp} MiB of old TRAP caches for ${language}.` + ); + } + } + return { trap_cache_cleanup_size_bytes: totalBytesCleanedUp }; + } catch (e) { + if (asHTTPError(e)?.status === 403) { + logger.warning( + `Could not cleanup TRAP caches as the token did not have the required permissions. To clean up TRAP caches, ensure the token has the "actions:write" permission. See ${"https://docs.github.com/en/actions/using-jobs/assigning-permissions-to-jobs" /* ASSIGNING_PERMISSIONS_TO_JOBS */} for more information.` + ); + } else { + logger.info(`Failed to cleanup TRAP caches, continuing. Details: ${e}`); + } + return { trap_cache_cleanup_error: getErrorMessage(e) }; } - await setCppTrapCachingEnvironmentVariables(config, logger); - return config; } -function parseRegistries(registriesInput) { - try { - return registriesInput ? load(registriesInput) : void 0; - } catch { - throw new ConfigurationError( - "Invalid registries input. Must be a YAML string." - ); +async function getTrapCachesForLanguage(allCaches, language, logger) { + logger.debug(`Listing TRAP caches for ${language}`); + for (const cache of allCaches) { + if (!cache.created_at || !cache.id || !cache.key || !cache.size_in_bytes) { + throw new Error( + `An unexpected cache item was returned from the API that was missing one or more required fields: ${JSON.stringify(cache)}` + ); + } } -} -function parseRegistriesWithoutCredentials(registriesInput) { - return parseRegistries(registriesInput)?.map((r) => { - const { url: url2, packages, kind } = r; - return { url: url2, packages, kind }; + return allCaches.filter((cache) => { + return cache.key?.includes(`-${language}-`); }); } -function isLocal(configPath) { - if (configPath.indexOf("./") === 0) { - return true; +async function getLanguagesSupportingCaching(codeql, languages, logger) { + const result = []; + const resolveResult = await codeql.resolveLanguages(); + outer: for (const lang of languages) { + const extractorsForLanguage = resolveResult.extractors[lang]; + if (extractorsForLanguage === void 0) { + logger.info( + `${lang} does not support TRAP caching (couldn't find an extractor)` + ); + continue; + } + if (extractorsForLanguage.length !== 1) { + logger.info( + `${lang} does not support TRAP caching (found multiple extractors)` + ); + continue; + } + const extractor = extractorsForLanguage[0]; + const trapCacheOptions = extractor.extractor_options?.trap?.properties?.cache?.properties; + if (trapCacheOptions === void 0) { + logger.info( + `${lang} does not support TRAP caching (missing option group)` + ); + continue; + } + for (const requiredOpt of ["dir", "bound", "write"]) { + if (!(requiredOpt in trapCacheOptions)) { + logger.info( + `${lang} does not support TRAP caching (missing ${requiredOpt} option)` + ); + continue outer; + } + } + result.push(lang); } - return configPath.indexOf("@") === -1; + return result; } -function getLocalConfig(logger, configFile, validateConfig) { - if (!fs9.existsSync(configFile)) { - throw new ConfigurationError( - getConfigFileDoesNotExistErrorMessage(configFile) - ); - } - return parseUserConfig( - logger, - configFile, - fs9.readFileSync(configFile, "utf-8"), - validateConfig - ); +async function cacheKey(codeql, language, baseSha) { + return `${await cachePrefix(codeql, language)}${baseSha}`; } -async function getRemoteConfig(logger, configFile, apiDetails, validateConfig) { - const format = new RegExp( - "(?[^/]+)/(?[^/]+)/(?[^@]+)@(?.*)" +async function cachePrefix(codeql, language) { + return `${CODEQL_TRAP_CACHE_PREFIX}-${CACHE_VERSION}-${(await codeql.getVersion()).version}-${language}-`; +} + +// src/config-utils.ts +var OVERLAY_MINIMUM_AVAILABLE_DISK_SPACE_MB = 2e4; +var OVERLAY_MINIMUM_AVAILABLE_DISK_SPACE_BYTES = OVERLAY_MINIMUM_AVAILABLE_DISK_SPACE_MB * 1e6; +var OVERLAY_MINIMUM_AVAILABLE_DISK_SPACE_V2_MB = 14e3; +var OVERLAY_MINIMUM_AVAILABLE_DISK_SPACE_V2_BYTES = OVERLAY_MINIMUM_AVAILABLE_DISK_SPACE_V2_MB * 1e6; +var OVERLAY_MINIMUM_MEMORY_MB = 5 * 1024; +var CODEQL_VERSION_REDUCED_OVERLAY_MEMORY_USAGE = "2.24.3"; +async function getSupportedLanguageMap(codeql, logger) { + const resolveSupportedLanguagesUsingCli = await codeql.supportsFeature( + "builtinExtractorsSpecifyDefaultQueries" /* BuiltinExtractorsSpecifyDefaultQueries */ ); - const pieces = format.exec(configFile); - if (pieces?.groups === void 0 || pieces.length < 5) { - throw new ConfigurationError( - getConfigFileRepoFormatInvalidMessage(configFile) - ); - } - const response = await getApiClientWithExternalAuth(apiDetails).rest.repos.getContent({ - owner: pieces.groups.owner, - repo: pieces.groups.repo, - path: pieces.groups.path, - ref: pieces.groups.ref + const resolveResult = await codeql.resolveLanguages({ + filterToLanguagesWithQueries: resolveSupportedLanguagesUsingCli }); - let fileContents; - if ("content" in response.data && response.data.content !== void 0) { - fileContents = response.data.content; - } else if (Array.isArray(response.data)) { - throw new ConfigurationError( - getConfigFileDirectoryGivenMessage(configFile) - ); - } else { - throw new ConfigurationError( - getConfigFileFormatInvalidMessage(configFile) + if (resolveSupportedLanguagesUsingCli) { + logger.debug( + `The CodeQL CLI supports the following languages: ${Object.keys(resolveResult.extractors).join(", ")}` ); } - return parseUserConfig( - logger, - configFile, - Buffer.from(fileContents, "base64").toString("binary"), - validateConfig - ); -} -function getPathToParsedConfigFile(tempDir) { - return path10.join(tempDir, "config"); -} -async function saveConfig(config, logger) { - const configString = JSON.stringify(config); - const configFile = getPathToParsedConfigFile(config.tempDir); - fs9.mkdirSync(path10.dirname(configFile), { recursive: true }); - fs9.writeFileSync(configFile, configString, "utf8"); - logger.debug("Saved config:"); - logger.debug(configString); -} -async function getConfig(tempDir, logger) { - const configFile = getPathToParsedConfigFile(tempDir); - if (!fs9.existsSync(configFile)) { - return void 0; - } - const configString = fs9.readFileSync(configFile, "utf8"); - logger.debug("Loaded config:"); - logger.debug(configString); - const config = JSON.parse(configString); - if (config.version === void 0) { - throw new ConfigurationError( - `Loaded configuration file, but it does not contain the expected 'version' field.` - ); + const supportedLanguages = {}; + for (const extractor of Object.keys(resolveResult.extractors)) { + if (resolveSupportedLanguagesUsingCli || BuiltInLanguage[extractor] !== void 0) { + supportedLanguages[extractor] = extractor; + } } - if (config.version !== getActionVersion()) { - throw new ConfigurationError( - `Loaded a configuration file for version '${config.version}', but running version '${getActionVersion()}'` - ); + if (resolveResult.aliases) { + for (const [alias, extractor] of Object.entries(resolveResult.aliases)) { + supportedLanguages[alias] = extractor; + } } - return config; + return supportedLanguages; } -async function generateRegistries(registriesInput, tempDir, logger) { - const registries = parseRegistries(registriesInput); - let registriesAuthTokens; - let qlconfigFile; - if (registries) { - const qlconfig = createRegistriesBlock(registries); - qlconfigFile = path10.join(tempDir, "qlconfig.yml"); - const qlconfigContents = dump(qlconfig); - fs9.writeFileSync(qlconfigFile, qlconfigContents, "utf8"); - logger.debug("Generated qlconfig.yml:"); - logger.debug(qlconfigContents); - registriesAuthTokens = registries.map((registry) => `${registry.url}=${registry.token}`).join(","); - } - if (typeof process.env.CODEQL_REGISTRIES_AUTH === "string") { - logger.debug( - "Using CODEQL_REGISTRIES_AUTH environment variable to authenticate with registries." - ); - } - return { - registriesAuthTokens: ( - // if the user has explicitly set the CODEQL_REGISTRIES_AUTH env var then use that - process.env.CODEQL_REGISTRIES_AUTH ?? registriesAuthTokens - ), - qlconfigFile - }; +var baseWorkflowsPath = ".github/workflows"; +function hasActionsWorkflows(sourceRoot) { + const workflowsPath = path10.resolve(sourceRoot, baseWorkflowsPath); + const stats = fs9.lstatSync(workflowsPath, { throwIfNoEntry: false }); + return stats !== void 0 && stats.isDirectory() && fs9.readdirSync(workflowsPath).length > 0; } -function createRegistriesBlock(registries) { - if (!Array.isArray(registries) || registries.some((r) => !r.url || !r.packages)) { - throw new ConfigurationError( - "Invalid 'registries' input. Must be an array of objects with 'url' and 'packages' properties." - ); +async function getRawLanguagesInRepo(repository, sourceRoot, logger) { + logger.debug( + `Automatically detecting languages (${repository.owner}/${repository.repo})` + ); + const response = await getApiClient().rest.repos.listLanguages({ + owner: repository.owner, + repo: repository.repo + }); + logger.debug(`Languages API response: ${JSON.stringify(response)}`); + const result = Object.keys(response.data).map( + (language) => language.trim().toLowerCase() + ); + if (hasActionsWorkflows(sourceRoot)) { + logger.debug(`Found a .github/workflows directory`); + result.push("actions"); } - const safeRegistries = registries.map((registry) => ({ - // ensure the url ends with a slash to avoid a bug in the CLI 2.10.4 - url: !registry?.url.endsWith("/") ? `${registry.url}/` : registry.url, - packages: registry.packages, - kind: registry.kind - })); - const qlconfig = { - registries: safeRegistries - }; - return qlconfig; + logger.debug(`Raw languages in repository: ${result.join(", ")}`); + return result; } -async function wrapEnvironment(env, operation) { - const oldEnv = { ...process.env }; - for (const [key, value] of Object.entries(env)) { - if (value !== void 0) { - process.env[key] = value; - } - } - try { - await operation(); - } finally { - for (const [key, value] of Object.entries(oldEnv)) { - process.env[key] = value; +async function getLanguages(codeql, languagesInput, repository, sourceRoot, logger) { + const { rawLanguages, autodetected } = await getRawLanguages( + languagesInput, + repository, + sourceRoot, + logger + ); + const languageMap = await getSupportedLanguageMap(codeql, logger); + const languagesSet = /* @__PURE__ */ new Set(); + const unknownLanguages = []; + for (const language of rawLanguages) { + const extractorName = languageMap[language]; + if (extractorName === void 0) { + unknownLanguages.push(language); + } else { + languagesSet.add(extractorName); } } -} -async function parseBuildModeInput(input, languages, features, logger) { - if (input === void 0) { - return void 0; - } - if (!Object.values(BuildMode).includes(input)) { + const languages = Array.from(languagesSet); + if (!autodetected && unknownLanguages.length > 0) { throw new ConfigurationError( - `Invalid build mode: '${input}'. Supported build modes are: ${Object.values( - BuildMode - ).join(", ")}.` - ); - } - if (languages.includes("csharp" /* csharp */) && await features.getValue("disable_csharp_buildless" /* DisableCsharpBuildless */)) { - logger.warning( - "Scanning C# code without a build is temporarily unavailable. Falling back to 'autobuild' build mode." - ); - return "autobuild" /* Autobuild */; - } - if (languages.includes("java" /* java */) && await features.getValue("disable_java_buildless_enabled" /* DisableJavaBuildlessEnabled */)) { - logger.warning( - "Scanning Java code without a build is temporarily unavailable. Falling back to 'autobuild' build mode." + getUnknownLanguagesError(unknownLanguages) ); - return "autobuild" /* Autobuild */; } - return input; -} -function appendExtraQueryExclusions(extraQueryExclusions, cliConfig) { - const augmentedConfig = cloneObject(cliConfig); - if (extraQueryExclusions.length === 0) { - return augmentedConfig; + if (languages.length === 0) { + throw new ConfigurationError(getNoLanguagesError()); } - augmentedConfig["query-filters"] = [ - // Ordering matters. If the first filter is an inclusion, it implicitly - // excludes all queries that are not included. If it is an exclusion, - // it implicitly includes all queries that are not excluded. So user - // filters (if any) should always be first to preserve intent. - ...augmentedConfig["query-filters"] || [], - ...extraQueryExclusions - ]; - if (augmentedConfig["query-filters"]?.length === 0) { - delete augmentedConfig["query-filters"]; + if (autodetected) { + logger.info(`Autodetected languages: ${languages.join(", ")}`); + } else { + logger.info(`Languages from configuration: ${languages.join(", ")}`); } - return augmentedConfig; -} -function isCodeScanningEnabled(config) { - return config.analysisKinds.includes("code-scanning" /* CodeScanning */); + return languages; } -function isCodeQualityEnabled(config) { - return config.analysisKinds.includes("code-quality" /* CodeQuality */); +function getRawLanguagesNoAutodetect(languagesInput) { + return (languagesInput || "").split(",").map((x) => x.trim().toLowerCase()).filter((x) => x.length > 0); } -function isRiskAssessmentEnabled(config) { - return config.analysisKinds.includes("risk-assessment" /* RiskAssessment */); +async function getRawLanguages(languagesInput, repository, sourceRoot, logger) { + const languagesFromInput = getRawLanguagesNoAutodetect(languagesInput); + if (languagesFromInput.length > 0) { + return { rawLanguages: languagesFromInput, autodetected: false }; + } + return { + rawLanguages: await getRawLanguagesInRepo(repository, sourceRoot, logger), + autodetected: true + }; } -function getPrimaryAnalysisKind(config) { - if (config.analysisKinds.length === 1) { - return config.analysisKinds[0]; +async function initActionState({ + languagesInput, + queriesInput, + packsInput, + buildModeInput, + dbLocation, + dependencyCachingEnabled, + debugMode, + debugArtifactName, + debugDatabaseName, + repository, + tempDir, + codeql, + sourceRoot, + githubVersion, + features, + repositoryProperties, + analysisKinds, + logger, + enableFileCoverageInformation +}, userConfig) { + const languages = await getLanguages( + codeql, + languagesInput, + repository, + sourceRoot, + logger + ); + const buildMode = await parseBuildModeInput( + buildModeInput, + languages, + features, + logger + ); + const augmentationProperties = await calculateAugmentation( + packsInput, + queriesInput, + 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 + }; } - return isCodeScanningEnabled(config) ? "code-scanning" /* CodeScanning */ : "code-quality" /* CodeQuality */; + const computedConfig = generateCodeScanningConfig( + logger, + userConfig, + augmentationProperties + ); + return { + version: getActionVersion(), + analysisKinds, + languages, + buildMode, + originalUserInput: userConfig, + computedConfig, + tempDir, + codeQLCmd: codeql.getPath(), + gitHubVersion: githubVersion, + dbLocation: dbLocationOrDefault(dbLocation, tempDir), + debugMode, + debugArtifactName, + debugDatabaseName, + trapCaches: {}, + trapCacheDownloadTime: 0, + dependencyCachingEnabled: getCachingKind(dependencyCachingEnabled), + dependencyCachingRestoredKeys: [], + extraQueryExclusions: [], + overlayDatabaseMode: "none" /* None */, + useOverlayDatabaseCaching: false, + overlayModeSetExplicitly: false, + repositoryProperties, + enableFileCoverageInformation + }; } -function getPrimaryAnalysisConfig(config) { - return getAnalysisConfig(getPrimaryAnalysisKind(config)); +async function downloadCacheWithTime(codeQL, languages, logger) { + const start = import_perf_hooks.performance.now(); + const trapCaches = await downloadTrapCaches(codeQL, languages, logger); + const trapCacheDownloadTime = import_perf_hooks.performance.now() - start; + return { trapCaches, trapCacheDownloadTime }; } -async function logGitVersionTelemetry(config, gitVersion) { - if (config.languages.length > 0) { - addNoLanguageDiagnostic( - config, - makeTelemetryDiagnostic( - "codeql-action/git-version-telemetry", - "Git version telemetry", - { - fullVersion: gitVersion.fullVersion, - truncatedVersion: gitVersion.truncatedVersion - } - ) +async function loadUserConfig(actionState, configFile, workspacePath, apiDetails, tempDir) { + if (isLocal(configFile)) { + if (configFile !== userConfigFromActionPath(tempDir)) { + configFile = path10.resolve(workspacePath, configFile); + if (!(configFile + path10.sep).startsWith(workspacePath + path10.sep)) { + throw new ConfigurationError( + getConfigFileOutsideWorkspaceErrorMessage(configFile) + ); + } + } + const validateConfig = await actionState.features.getValue( + "validate_db_config" /* ValidateDbConfig */ ); + return getLocalConfig(actionState.logger, configFile, validateConfig); + } else { + return await getRemoteConfig(actionState, configFile, apiDetails); } } -async function logGeneratedFilesTelemetry(config, duration, generatedFilesCount) { - if (config.languages.length < 1) { - return; +var OVERLAY_ANALYSIS_FEATURES = { + cpp: "overlay_analysis_cpp" /* OverlayAnalysisCpp */, + csharp: "overlay_analysis_csharp" /* OverlayAnalysisCsharp */, + go: "overlay_analysis_go" /* OverlayAnalysisGo */, + java: "overlay_analysis_java" /* OverlayAnalysisJava */, + javascript: "overlay_analysis_javascript" /* OverlayAnalysisJavascript */, + python: "overlay_analysis_python" /* OverlayAnalysisPython */, + ruby: "overlay_analysis_ruby" /* OverlayAnalysisRuby */ +}; +var OVERLAY_ANALYSIS_CODE_SCANNING_FEATURES = { + cpp: "overlay_analysis_code_scanning_cpp" /* OverlayAnalysisCodeScanningCpp */, + csharp: "overlay_analysis_code_scanning_csharp" /* OverlayAnalysisCodeScanningCsharp */, + go: "overlay_analysis_code_scanning_go" /* OverlayAnalysisCodeScanningGo */, + java: "overlay_analysis_code_scanning_java" /* OverlayAnalysisCodeScanningJava */, + javascript: "overlay_analysis_code_scanning_javascript" /* OverlayAnalysisCodeScanningJavascript */, + python: "overlay_analysis_code_scanning_python" /* OverlayAnalysisCodeScanningPython */, + ruby: "overlay_analysis_code_scanning_ruby" /* OverlayAnalysisCodeScanningRuby */ +}; +async function checkOverlayAnalysisFeatureEnabled(features, codeql, languages, codeScanningConfig) { + if (!await features.getValue("overlay_analysis" /* OverlayAnalysis */, codeql)) { + return new Failure("overall-feature-not-enabled" /* OverallFeatureNotEnabled */); } - addNoLanguageDiagnostic( - config, - makeTelemetryDiagnostic( - "codeql-action/generated-files-telemetry", - "Generated files telemetry", - { - duration, - generatedFilesCount - } - ) - ); + let enableForCodeScanningOnly = false; + for (const language of languages) { + const feature = OVERLAY_ANALYSIS_FEATURES[language]; + if (feature && await features.getValue(feature, codeql)) { + continue; + } + const codeScanningFeature = OVERLAY_ANALYSIS_CODE_SCANNING_FEATURES[language]; + if (codeScanningFeature && await features.getValue(codeScanningFeature, codeql)) { + enableForCodeScanningOnly = true; + continue; + } + return new Failure("language-not-enabled" /* LanguageNotEnabled */); + } + if (enableForCodeScanningOnly) { + const usesDefaultQueriesOnly = codeScanningConfig["disable-default-queries"] !== true && codeScanningConfig.packs === void 0 && codeScanningConfig.queries === void 0 && codeScanningConfig["query-filters"] === void 0; + if (!usesDefaultQueriesOnly) { + return new Failure("non-default-queries" /* NonDefaultQueries */); + } + } + return new Success(void 0); } - -// src/status-report.ts -function getDisplayActionName(actionName) { - if (actionName === "finish" /* Analyze */) { - return "analyze"; +function runnerHasSufficientDiskSpace(diskUsage, logger, useV2ResourceChecks) { + const minimumDiskSpaceBytes = useV2ResourceChecks ? OVERLAY_MINIMUM_AVAILABLE_DISK_SPACE_V2_BYTES : OVERLAY_MINIMUM_AVAILABLE_DISK_SPACE_BYTES; + if (diskUsage.numAvailableBytes < minimumDiskSpaceBytes) { + const diskSpaceMb = Math.round(diskUsage.numAvailableBytes / 1e6); + const minimumDiskSpaceMb = Math.round(minimumDiskSpaceBytes / 1e6); + logger.info( + `Setting overlay database mode to ${"none" /* None */} due to insufficient disk space (${diskSpaceMb} MB, needed ${minimumDiskSpaceMb} MB).` + ); + return false; } - return actionName; + return true; } -function isFirstPartyAnalysis(actionName) { - if (actionName !== "upload-sarif" /* UploadSarif */) { +async function runnerHasSufficientMemory(codeql, ramInput, logger) { + if (await codeQlVersionAtLeast( + codeql, + CODEQL_VERSION_REDUCED_OVERLAY_MEMORY_USAGE + )) { + logger.debug( + `Skipping memory check for overlay analysis because CodeQL version is at least ${CODEQL_VERSION_REDUCED_OVERLAY_MEMORY_USAGE}.` + ); return true; } - return process.env["CODEQL_ACTION_INIT_HAS_RUN" /* INIT_ACTION_HAS_RUN */] === "true"; -} -function isThirdPartyAnalysis(actionName) { - return !isFirstPartyAnalysis(actionName); + const memoryFlagValue = getCodeQLMemoryLimit(ramInput, logger); + if (memoryFlagValue < OVERLAY_MINIMUM_MEMORY_MB) { + logger.info( + `Setting overlay database mode to ${"none" /* None */} due to insufficient memory for CodeQL analysis (${memoryFlagValue} MB, needed ${OVERLAY_MINIMUM_MEMORY_MB} MB).` + ); + return false; + } + logger.debug( + `Memory available for CodeQL analysis is ${memoryFlagValue} MB, which is above the minimum of ${OVERLAY_MINIMUM_MEMORY_MB} MB.` + ); + return true; } -var JobStatus = /* @__PURE__ */ ((JobStatus2) => { - JobStatus2["UnknownStatus"] = "JOB_STATUS_UNKNOWN"; - JobStatus2["SuccessStatus"] = "JOB_STATUS_SUCCESS"; - JobStatus2["FailureStatus"] = "JOB_STATUS_FAILURE"; - JobStatus2["ConfigErrorStatus"] = "JOB_STATUS_CONFIGURATION_ERROR"; - return JobStatus2; -})(JobStatus || {}); -function getActionsStatus(error3, otherFailureCause) { - if (error3 || otherFailureCause) { - return error3 instanceof ConfigurationError ? "user-error" : "failure"; - } else { - return "success"; +async function checkRunnerResources(codeql, diskUsage, ramInput, logger, useV2ResourceChecks) { + if (!runnerHasSufficientDiskSpace(diskUsage, logger, useV2ResourceChecks)) { + return new Failure("insufficient-disk-space" /* InsufficientDiskSpace */); } -} -function getJobStatusDisplayName(status) { - switch (status) { - case "JOB_STATUS_SUCCESS" /* SuccessStatus */: - return "success"; - case "JOB_STATUS_FAILURE" /* FailureStatus */: - return "failure"; - case "JOB_STATUS_CONFIGURATION_ERROR" /* ConfigErrorStatus */: - return "configuration error"; - case "JOB_STATUS_UNKNOWN" /* UnknownStatus */: - return "unknown"; - default: - assertNever(status); + if (!await runnerHasSufficientMemory(codeql, ramInput, logger)) { + return new Failure("insufficient-memory" /* InsufficientMemory */); } + return new Success(void 0); } -function setJobStatusIfUnsuccessful(actionStatus) { - if (actionStatus === "user-error") { - core9.exportVariable( - "CODEQL_ACTION_JOB_STATUS" /* JOB_STATUS */, - process.env["CODEQL_ACTION_JOB_STATUS" /* JOB_STATUS */] ?? "JOB_STATUS_CONFIGURATION_ERROR" /* ConfigErrorStatus */ +async function checkOverlayEnablement(codeql, features, languages, sourceRoot, buildMode, ramInput, codeScanningConfig, repositoryProperties, gitVersion, logger) { + const modeEnv = process.env.CODEQL_OVERLAY_DATABASE_MODE; + if (modeEnv === "overlay" /* Overlay */ || modeEnv === "overlay-base" /* OverlayBase */ || modeEnv === "none" /* None */) { + logger.info( + `Setting overlay database mode to ${modeEnv} from the CODEQL_OVERLAY_DATABASE_MODE environment variable.` ); - } else if (actionStatus === "failure" || actionStatus === "aborted") { - core9.exportVariable( - "CODEQL_ACTION_JOB_STATUS" /* JOB_STATUS */, - process.env["CODEQL_ACTION_JOB_STATUS" /* JOB_STATUS */] ?? "JOB_STATUS_FAILURE" /* FailureStatus */ + if (modeEnv === "none" /* None */) { + return new Failure("disabled-by-environment-variable" /* DisabledByEnvironmentVariable */); + } + return validateOverlayDatabaseMode( + modeEnv, + false, + true, + codeql, + languages, + sourceRoot, + buildMode, + gitVersion, + logger ); } -} -async function createStatusReportBase(actionName, status, actionStartedAt, config, diskInfo, logger, cause, exception) { - try { - const commitOid = getOptionalInput("sha") || process.env["GITHUB_SHA"] || ""; - const ref = await getRef(); - const jobRunUUID = process.env["JOB_RUN_UUID" /* JOB_RUN_UUID */] || ""; - const workflowRunID = getWorkflowRunID(); - const workflowRunAttempt = getWorkflowRunAttempt(); - const workflowName = process.env["GITHUB_WORKFLOW"] || ""; - const jobName = process.env["GITHUB_JOB"] || ""; - const analysis_key = await getAnalysisKey(); - let workflowStartedAt = process.env["CODEQL_WORKFLOW_STARTED_AT" /* WORKFLOW_STARTED_AT */]; - if (workflowStartedAt === void 0) { - workflowStartedAt = actionStartedAt.toISOString(); - core9.exportVariable("CODEQL_WORKFLOW_STARTED_AT" /* WORKFLOW_STARTED_AT */, workflowStartedAt); - } - const runnerOs = getRequiredEnvParam("RUNNER_OS"); - const codeQlCliVersion = getCachedCodeQlVersion(); - const actionRef = process.env["GITHUB_ACTION_REF"] || ""; - const testingEnvironment = getTestingEnvironment(); - if (testingEnvironment) { - core9.exportVariable("CODEQL_ACTION_TESTING_ENVIRONMENT" /* TESTING_ENVIRONMENT */, testingEnvironment); - } - const isSteadyStateDefaultSetupRun = process.env["CODE_SCANNING_IS_STEADY_STATE_DEFAULT_SETUP"] === "true"; - const statusReport = { - action_name: actionName, - action_oid: "unknown", - // TODO decide if it's possible to fill this in - action_ref: actionRef, - action_started_at: actionStartedAt.toISOString(), - action_version: getActionVersion(), - analysis_kinds: config?.analysisKinds?.join(","), - analysis_key, - build_mode: config?.buildMode, - commit_oid: commitOid, - first_party_analysis: isFirstPartyAnalysis(actionName), - job_name: jobName, - job_run_uuid: jobRunUUID, - ref, - runner_os: runnerOs, - started_at: workflowStartedAt, - status, - steady_state_default_setup: isSteadyStateDefaultSetupRun, - testing_environment: testingEnvironment || "", - workflow_name: workflowName, - workflow_run_attempt: workflowRunAttempt, - workflow_run_id: workflowRunID - }; - try { - statusReport.actions_event_name = getWorkflowEventName(); - } catch (e) { - logger.warning( - `Could not determine the workflow event name: ${getErrorMessage(e)}.` - ); - } - if (config) { - statusReport.languages = config.languages?.join(","); - } - if (diskInfo) { - statusReport.runner_available_disk_space_bytes = diskInfo.numAvailableBytes; - statusReport.runner_total_disk_space_bytes = diskInfo.numTotalBytes; - } - if (cause) { - statusReport.cause = cause; - } - if (exception) { - statusReport.exception = exception; - } - if (status === "success" || status === "failure" || status === "aborted" || status === "user-error") { - statusReport.completed_at = (/* @__PURE__ */ new Date()).toISOString(); - } - const matrix = getRequiredInput("matrix"); - if (matrix) { - statusReport.matrix_vars = matrix; - } - if ("RUNNER_ARCH" in process.env) { - statusReport.runner_arch = process.env["RUNNER_ARCH"]; - } - if (!(runnerOs === "Linux" && isSelfHostedRunner())) { - statusReport.runner_os_release = os3.release(); - } - if (codeQlCliVersion !== void 0) { - statusReport.codeql_version = codeQlCliVersion.version; - } - const imageVersion = process.env["ImageVersion"]; - if (imageVersion) { - statusReport.runner_image_version = imageVersion; - } - return statusReport; - } catch (e) { + if (repositoryProperties["github-codeql-disable-overlay" /* DISABLE_OVERLAY */] === true) { + logger.info( + `Setting overlay database mode to ${"none" /* None */} because the ${"github-codeql-disable-overlay" /* DISABLE_OVERLAY */} repository property is set to true.` + ); + return new Failure("disabled-by-repository-property" /* DisabledByRepositoryProperty */); + } + const featureResult = await checkOverlayAnalysisFeatureEnabled( + features, + codeql, + languages, + codeScanningConfig + ); + if (featureResult.isFailure()) { + return featureResult; + } + const performResourceChecks = !await features.getValue( + "overlay_analysis_skip_resource_checks" /* OverlayAnalysisSkipResourceChecks */, + codeql + ); + const useV2ResourceChecks = await features.getValue( + "overlay_analysis_resource_checks_v2" /* OverlayAnalysisResourceChecksV2 */ + ); + const checkOverlayStatus = await features.getValue( + "overlay_analysis_status_check" /* OverlayAnalysisStatusCheck */ + ); + const needDiskUsage = performResourceChecks || checkOverlayStatus; + const diskUsage = needDiskUsage ? await checkDiskUsage(logger) : void 0; + if (needDiskUsage && diskUsage === void 0) { logger.warning( - `Failed to gather information for telemetry: ${getErrorMessage(e)}. Will skip sending status report.` + `Unable to determine disk usage, therefore setting overlay database mode to ${"none" /* None */}.` ); - if (isInTestMode()) { - throw e; - } - return void 0; + return new Failure("unable-to-determine-disk-usage" /* UnableToDetermineDiskUsage */); } -} -var OUT_OF_DATE_MSG = "CodeQL Action is out-of-date. Please upgrade to the latest version of `codeql-action`."; -var INCOMPATIBLE_MSG = "CodeQL Action version is incompatible with the API endpoint. Please update to a compatible version of `codeql-action`."; -async function sendStatusReport(statusReport) { - setJobStatusIfUnsuccessful(statusReport.status); - const statusReportJSON = JSON.stringify(statusReport); - core9.debug(`Sending status report: ${statusReportJSON}`); - if (isInTestMode()) { - core9.debug("In test mode. Status reports are not uploaded."); - return; + const resourceResult = performResourceChecks && diskUsage !== void 0 ? await checkRunnerResources( + codeql, + diskUsage, + ramInput, + logger, + useV2ResourceChecks + ) : new Success(void 0); + if (resourceResult.isFailure()) { + return resourceResult; } - const nwo = getRepositoryNwo(); - const client = getApiClient(); - try { - await client.request( - "PUT /repos/:owner/:repo/code-scanning/analysis/status", - { - owner: nwo.owner, - repo: nwo.repo, - data: statusReportJSON - } + if (checkOverlayStatus && diskUsage !== void 0 && await shouldSkipOverlayAnalysis(codeql, languages, diskUsage, logger)) { + logger.info( + `Setting overlay database mode to ${"none" /* None */} because overlay analysis previously failed with this combination of languages, disk space, and CodeQL version.` ); - } catch (e) { - const httpError = asHTTPError(e); - if (httpError !== void 0) { - switch (httpError.status) { - case 403: - if (getWorkflowEventName() === "push" && process.env["GITHUB_ACTOR"] === "dependabot[bot]") { - core9.warning( - `Workflows triggered by Dependabot on the "push" event run with read-only access. Uploading CodeQL results requires write access. To use CodeQL with Dependabot, please ensure you are using the "pull_request" event for this workflow and avoid triggering on the "push" event for Dependabot branches. See ${"https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/customizing-your-advanced-setup-for-code-scanning#scanning-on-push" /* SCANNING_ON_PUSH */} for more information on how to configure these events.` - ); - } else { - core9.warning( - `This run of the CodeQL Action does not have permission to access the CodeQL Action API endpoints. This could be because the Action is running on a pull request from a fork. If not, please ensure the workflow has at least the 'security-events: read' permission. Details: ${httpError.message}` - ); - } - return; - case 404: - core9.warning(httpError.message); - return; - case 422: - if (getRequiredEnvParam("GITHUB_SERVER_URL") !== GITHUB_DOTCOM_URL) { - core9.debug(INCOMPATIBLE_MSG); - } else { - core9.debug(OUT_OF_DATE_MSG); - } - return; - } - } - core9.warning( - `An unexpected error occurred when sending a status report: ${getErrorMessage( - e - )}` + return new Failure("skipped-due-to-cached-status" /* SkippedDueToCachedStatus */); + } + let overlayDatabaseMode; + if (isAnalyzingPullRequest()) { + overlayDatabaseMode = "overlay" /* Overlay */; + logger.info( + `Setting overlay database mode to ${overlayDatabaseMode} with caching because we are analyzing a pull request.` + ); + } else if (await isAnalyzingDefaultBranch()) { + overlayDatabaseMode = "overlay-base" /* OverlayBase */; + logger.info( + `Setting overlay database mode to ${overlayDatabaseMode} with caching because we are analyzing the default branch.` ); + } else { + return new Failure("not-pull-request-or-default-branch" /* NotPullRequestOrDefaultBranch */); } -} -async function createInitWithConfigStatusReport(config, initStatusReport, configFile, totalCacheSize, overlayBaseDatabaseStats, dependencyCachingResults) { - const languages = config.languages.join(","); - const paths = (config.originalUserInput.paths || []).join(","); - const pathsIgnore = (config.originalUserInput["paths-ignore"] || []).join( - "," + return validateOverlayDatabaseMode( + overlayDatabaseMode, + true, + false, + codeql, + languages, + sourceRoot, + buildMode, + gitVersion, + logger ); - const disableDefaultQueries = config.originalUserInput["disable-default-queries"] ? languages : ""; - const queries = []; - let queriesInput = getOptionalInput("queries")?.trim(); - if (queriesInput === void 0 || queriesInput.startsWith("+")) { - queries.push( - ...(config.originalUserInput.queries || []).map((q) => q.uses) +} +async function validateOverlayDatabaseMode(overlayDatabaseMode, useOverlayDatabaseCaching, overlayModeSetExplicitly, codeql, languages, sourceRoot, buildMode, gitVersion, logger) { + if (buildMode !== "none" /* None */ && (await Promise.all( + languages.map( + async (l) => l !== "go" /* go */ && // Workaround to allow overlay analysis for Go with any build + // mode, since it does not yet support BMN. The Go autobuilder and/or extractor will + // ensure that overlay-base databases are only created for supported Go build setups, + // and that we'll fall back to full databases in other cases. + await codeql.isTracedLanguage(l) + ) + )).some(Boolean)) { + logger.warning( + `Cannot build an ${overlayDatabaseMode} database because build-mode is set to "${buildMode}" instead of "none". Falling back to creating a normal full database instead.` ); + return new Failure("incompatible-build-mode" /* IncompatibleBuildMode */); } - if (queriesInput !== void 0) { - queriesInput = queriesInput.startsWith("+") ? queriesInput.slice(1) : queriesInput; - queries.push(...queriesInput.split(",")); - } - let packs = {}; - if (Array.isArray(config.computedConfig.packs)) { - packs[config.languages[0]] = config.computedConfig.packs; - } else if (config.computedConfig.packs !== void 0) { - packs = config.computedConfig.packs; - } - return { - ...initStatusReport, - config_file: configFile ?? "", - disable_default_queries: disableDefaultQueries, - paths, - paths_ignore: pathsIgnore, - queries: queries.join(","), - packs: JSON.stringify(packs), - trap_cache_languages: Object.keys(config.trapCaches).join(","), - trap_cache_download_size_bytes: totalCacheSize, - trap_cache_download_duration_ms: Math.round(config.trapCacheDownloadTime), - overlay_base_database_download_size_bytes: overlayBaseDatabaseStats?.databaseSizeBytes, - overlay_base_database_download_duration_ms: overlayBaseDatabaseStats?.databaseDownloadDurationMs, - dependency_caching_restore_results: dependencyCachingResults, - query_filters: JSON.stringify( - config.originalUserInput["query-filters"] ?? [] - ), - registries: JSON.stringify( - parseRegistriesWithoutCredentials(getOptionalInput("registries")) ?? [] - ) - }; -} -async function sendUnhandledErrorStatusReport(actionName, actionStartedAt, error3, logger) { - try { - const statusReport = await createStatusReportBase( - actionName, - "failure", - actionStartedAt, - void 0, - void 0, - logger, - `Unhandled CodeQL Action error: ${getErrorMessage(error3)}`, - error3 instanceof Error ? error3.stack : void 0 + if (!await codeQlVersionAtLeast(codeql, CODEQL_OVERLAY_MINIMUM_VERSION)) { + logger.warning( + `Cannot build an ${overlayDatabaseMode} database because the CodeQL CLI is older than ${CODEQL_OVERLAY_MINIMUM_VERSION}. Falling back to creating a normal full database instead.` ); - if (statusReport !== void 0) { - await sendStatusReport(statusReport); - } - } catch (e) { + return new Failure("incompatible-codeql" /* IncompatibleCodeQl */); + } + const gitRoot = await getGitRoot(sourceRoot); + if (gitRoot === void 0) { logger.warning( - `Failed to send the unhandled error status report: ${getErrorMessage(e)}.` + `Cannot build an ${overlayDatabaseMode} database because the source root "${sourceRoot}" is not inside a git repository. Falling back to creating a normal full database instead.` ); - if (isInTestMode()) { - throw e; + return new Failure("no-git-root" /* NoGitRoot */); + } + if (hasSubmodules(gitRoot)) { + if (gitVersion === void 0) { + logger.warning( + `Cannot build an ${overlayDatabaseMode} database because the repository has submodules and the Git version could not be determined. Falling back to creating a normal full database instead.` + ); + return new Failure("incompatible-git" /* IncompatibleGit */); + } + if (!gitVersion.isAtLeast(GIT_MINIMUM_VERSION_FOR_OVERLAY_WITH_SUBMODULES)) { + logger.warning( + `Cannot build an ${overlayDatabaseMode} database because the repository has submodules and the installed Git version is older than ${GIT_MINIMUM_VERSION_FOR_OVERLAY_WITH_SUBMODULES}. Falling back to creating a normal full database instead.` + ); + return new Failure("incompatible-git" /* IncompatibleGit */); } } + return new Success({ + overlayDatabaseMode, + useOverlayDatabaseCaching, + overlayModeSetExplicitly + }); } - -// src/action-common.ts -async function runInActions(action) { - const startedAt = /* @__PURE__ */ new Date(); - const logger = getActionsLogger(); - const env = getEnv(); - const actionsEnv = getActionsEnv(); - try { - await action.run({ - name: action.name, - startedAt, - logger, - env, - actions: actionsEnv - }); - } catch (error3) { - core10.setFailed( - `${getDisplayActionName(action.name)} action failed: ${getErrorMessage(error3)}` - ); - await sendUnhandledErrorStatusReport(action.name, startedAt, error3, logger); +async function isTrapCachingEnabled(features, overlayDatabaseMode) { + const trapCaching = getOptionalInput("trap-caching"); + if (trapCaching !== void 0) return trapCaching === "true"; + if (!isHostedRunner()) return false; + if (overlayDatabaseMode !== "none" /* None */ && await features.getValue("overlay_analysis_disable_trap_caching" /* OverlayAnalysisDisableTrapCaching */)) { + return false; } + return true; } - -// src/analyze.ts -var fs16 = __toESM(require("fs")); -var path15 = __toESM(require("path")); -var import_perf_hooks3 = require("perf_hooks"); -var io5 = __toESM(require_io()); - -// src/autobuild.ts -var core13 = __toESM(require_core()); - -// src/codeql.ts -var fs15 = __toESM(require("fs")); -var path14 = __toESM(require("path")); -var core12 = __toESM(require_core()); -var toolrunner3 = __toESM(require_toolrunner()); - -// src/cli-errors.ts -var SUPPORTED_PLATFORMS = [ - ["linux", "x64"], - ["win32", "x64"], - ["darwin", "x64"], - ["darwin", "arm64"] -]; -var CliError = class extends Error { - exitCode; - stderr; - constructor({ cmd, args, exitCode, stderr }) { - const prettyCommand = prettyPrintInvocation(cmd, args); - const fatalErrors = extractFatalErrors(stderr); - const autobuildErrors = extractAutobuildErrors(stderr); - let message; - if (fatalErrors) { - message = `Encountered a fatal error while running "${prettyCommand}". Exit code was ${exitCode} and error was: ${ensureEndsInPeriod( - fatalErrors.trim() - )} See the logs for more details.`; - } else if (autobuildErrors) { - message = `We were unable to automatically build your code. Please provide manual build steps. See ${"https://docs.github.com/en/code-security/code-scanning/troubleshooting-code-scanning/automatic-build-failed" /* AUTOMATIC_BUILD_FAILED */} for more information. Encountered the following error: ${autobuildErrors}`; +async function setCppTrapCachingEnvironmentVariables(config, logger) { + if (config.languages.includes("cpp" /* cpp */)) { + const envVar = "CODEQL_EXTRACTOR_CPP_TRAP_CACHING"; + if (process.env[envVar]) { + logger.info( + `Environment variable ${envVar} already set, leaving it unchanged.` + ); + } else if (config.trapCaches["cpp" /* cpp */]) { + logger.info("Enabling TRAP caching for C/C++."); + core10.exportVariable(envVar, "true"); } else { - const lastLine = ensureEndsInPeriod( - stderr.trim().split("\n").pop()?.trim() || "n/a" + logger.debug(`Disabling TRAP caching for C/C++.`); + core10.exportVariable(envVar, "false"); + } + } +} +function dbLocationOrDefault(dbLocation, tempDir) { + return dbLocation || path10.resolve(tempDir, "codeql_databases"); +} +function userConfigFromActionPath(tempDir) { + return path10.resolve(tempDir, "user-config-from-action.yml"); +} +function hasQueryCustomisation(userConfig) { + return isDefined2(userConfig["disable-default-queries"]) || isDefined2(userConfig.queries) || isDefined2(userConfig["query-filters"]); +} +async function applyIncrementalAnalysisSettings(config, hasDiffRanges, codeql, logger) { + if (config.overlayDatabaseMode === "overlay" /* Overlay */ && !hasDiffRanges && !config.overlayModeSetExplicitly) { + logger.info( + `Reverting overlay database mode to ${"none" /* None */} because the PR diff ranges could not be computed.` + ); + config.overlayDatabaseMode = "none" /* None */; + config.useOverlayDatabaseCaching = false; + await addOverlayDisablementDiagnostics( + config, + codeql, + "diff-informed-analysis-not-enabled" /* DiffInformedAnalysisNotEnabled */ + ); + } + if (hasDiffRanges) { + config.extraQueryExclusions.push({ + exclude: { tags: "exclude-from-incremental" } + }); + } +} +async function initConfig(actionState, inputs) { + const { logger, features } = actionState; + const { tempDir } = inputs; + if (inputs.configInput) { + if (inputs.configFile) { + logger.warning( + `Both a config file and config input were provided. Ignoring config file.` ); - message = `Encountered a fatal error while running "${prettyCommand}". Exit code was ${exitCode} and last log line was: ${lastLine} See the logs for more details.`; } - super(message); - this.exitCode = exitCode; - this.stderr = stderr; + inputs.configFile = userConfigFromActionPath(tempDir); + fs9.writeFileSync(inputs.configFile, inputs.configInput); + logger.debug(`Using config from action input: ${inputs.configFile}`); } -}; -function extractFatalErrors(error3) { - const fatalErrorRegex = /.*fatal (internal )?error occurr?ed(. Details)?:/gi; - let fatalErrors = []; - let lastFatalErrorIndex; - let match2; - while ((match2 = fatalErrorRegex.exec(error3)) !== null) { - if (lastFatalErrorIndex !== void 0) { - fatalErrors.push(error3.slice(lastFatalErrorIndex, match2.index).trim()); + let userConfig = {}; + if (!inputs.configFile) { + logger.debug("No configuration file was provided"); + } else { + logger.debug(`Using configuration file: ${inputs.configFile}`); + userConfig = await loadUserConfig( + actionState, + inputs.configFile, + inputs.workspacePath, + inputs.apiDetails, + tempDir + ); + } + const config = await initActionState(inputs, userConfig); + if (config.analysisKinds.length === 1 && isCodeQualityEnabled(config)) { + if (hasQueryCustomisation(config.computedConfig)) { + throw new ConfigurationError( + "Query customizations are unsupported, because only `code-quality` analysis is enabled." + ); } - lastFatalErrorIndex = match2.index; + const queries = codeQualityQueries.map((v) => ({ uses: v })); + config.computedConfig["disable-default-queries"] = true; + config.computedConfig.queries = queries; + config.computedConfig["query-filters"] = []; } - if (lastFatalErrorIndex !== void 0) { - const lastError = error3.slice(lastFatalErrorIndex).trim(); - if (fatalErrors.length === 0) { - return lastError; + let gitVersion = void 0; + try { + gitVersion = await getGitVersionOrThrow(); + logger.info(`Using Git version ${gitVersion.fullVersion}`); + await logGitVersionTelemetry(config, gitVersion); + } catch (e) { + logger.warning(`Could not determine Git version: ${getErrorMessage(e)}`); + if (isInTestMode() && process.env["CODEQL_ACTION_TOLERATE_MISSING_GIT_VERSION" /* TOLERATE_MISSING_GIT_VERSION */] !== "true") { + throw e; } - const isOneLiner = !fatalErrors.some((e) => e.includes("\n")); - if (isOneLiner) { - fatalErrors = fatalErrors.map(ensureEndsInPeriod); + } + if (await features.getValue("ignore_generated_files" /* IgnoreGeneratedFiles */) && isDynamicWorkflow()) { + try { + const generatedFilesCheckStartedAt = import_perf_hooks.performance.now(); + const generatedFiles = await getGeneratedFiles(inputs.sourceRoot); + const generatedFilesDuration = Math.round( + import_perf_hooks.performance.now() - generatedFilesCheckStartedAt + ); + if (generatedFiles.length > 0) { + config.computedConfig["paths-ignore"] ??= []; + config.computedConfig["paths-ignore"].push(...generatedFiles); + logger.info( + `Detected ${generatedFiles.length} generated file(s), which will be excluded from analysis: ${joinAtMost(generatedFiles, ", ", 10)}` + ); + } else { + logger.info(`Found no generated files.`); + } + await logGeneratedFilesTelemetry( + config, + generatedFilesDuration, + generatedFiles.length + ); + } catch (error3) { + logger.info(`Cannot ignore generated files: ${getErrorMessage(error3)}`); } - return [ - ensureEndsInPeriod(lastError), - "Context:", - ...fatalErrors.reverse() - ].join(isOneLiner ? " " : "\n"); + } else { + logger.debug(`Skipping check for generated files.`); } - return void 0; -} -function extractAutobuildErrors(error3) { - const pattern = /.*\[autobuild\] \[ERROR\] (.*)/gi; - let errorLines = [...error3.matchAll(pattern)].map((match2) => match2[1]); - if (errorLines.length > 10) { - errorLines = errorLines.slice(0, 10); - errorLines.push("(truncated)"); + const overlayDatabaseModeResult = await checkOverlayEnablement( + inputs.codeql, + inputs.features, + config.languages, + inputs.sourceRoot, + config.buildMode, + inputs.ramInput, + config.computedConfig, + config.repositoryProperties, + gitVersion, + logger + ); + if (overlayDatabaseModeResult.isSuccess()) { + const { + overlayDatabaseMode, + useOverlayDatabaseCaching, + overlayModeSetExplicitly + } = overlayDatabaseModeResult.value; + logger.info( + `Using overlay database mode: ${overlayDatabaseMode} ${useOverlayDatabaseCaching ? "with" : "without"} caching.` + ); + config.overlayDatabaseMode = overlayDatabaseMode; + config.useOverlayDatabaseCaching = useOverlayDatabaseCaching; + config.overlayModeSetExplicitly = overlayModeSetExplicitly; + } else { + const overlayDisabledReason = overlayDatabaseModeResult.value; + logger.info( + `Using overlay database mode: ${"none" /* None */} without caching.` + ); + config.overlayDatabaseMode = "none" /* None */; + config.useOverlayDatabaseCaching = false; + await addOverlayDisablementDiagnostics( + config, + inputs.codeql, + overlayDisabledReason + ); } - return errorLines.join("\n") || void 0; -} -var cliErrorsConfig = { - ["AutobuildError" /* AutobuildError */]: { - cliErrorMessageCandidates: [ - new RegExp("We were unable to automatically build your code") - ] - }, - ["CouldNotCreateTempDir" /* CouldNotCreateTempDir */]: { - cliErrorMessageCandidates: [new RegExp("Could not create temp directory")] - }, - ["ExternalRepositoryCloneFailed" /* ExternalRepositoryCloneFailed */]: { - cliErrorMessageCandidates: [ - new RegExp("Failed to clone external Git repository") - ] - }, - ["GradleBuildFailed" /* GradleBuildFailed */]: { - cliErrorMessageCandidates: [ - new RegExp("\\[autobuild\\] FAILURE: Build failed with an exception.") - ] - }, - // Version of CodeQL CLI is incompatible with this version of the CodeQL Action - ["IncompatibleWithActionVersion" /* IncompatibleWithActionVersion */]: { - cliErrorMessageCandidates: [ - new RegExp("is not compatible with this CodeQL CLI") - ] - }, - ["InitCalledTwice" /* InitCalledTwice */]: { - cliErrorMessageCandidates: [ - new RegExp( - "Refusing to create databases .* but could not process any of it" - ) - ], - additionalErrorMessageToAppend: `Is the "init" action called twice in the same job?` - }, - ["InvalidConfigFile" /* InvalidConfigFile */]: { - cliErrorMessageCandidates: [ - new RegExp("Config file .* is not valid"), - new RegExp("The supplied config file is empty") - ] - }, - ["InvalidExternalRepoSpecifier" /* InvalidExternalRepoSpecifier */]: { - cliErrorMessageCandidates: [ - new RegExp("Specifier for external repository is invalid") - ] - }, - // Expected source location for database creation does not exist - ["InvalidSourceRoot" /* InvalidSourceRoot */]: { - cliErrorMessageCandidates: [new RegExp("Invalid source root")] - }, - ["MavenBuildFailed" /* MavenBuildFailed */]: { - cliErrorMessageCandidates: [ - new RegExp("\\[autobuild\\] \\[ERROR\\] Failed to execute goal") - ] - }, - ["NoBuildCommandAutodetected" /* NoBuildCommandAutodetected */]: { - cliErrorMessageCandidates: [ - new RegExp("Could not auto-detect a suitable build method") - ] - }, - ["NoBuildMethodAutodetected" /* NoBuildMethodAutodetected */]: { - cliErrorMessageCandidates: [ - new RegExp( - "Could not detect a suitable build command for the source checkout" - ) - ] - }, - // Usually when a manual build script has failed, or if an autodetected language - // was unintended to have CodeQL analysis run on it. - ["NoSourceCodeSeen" /* NoSourceCodeSeen */]: { - exitCode: 32, - cliErrorMessageCandidates: [ - new RegExp( - "CodeQL detected code written in .* but could not process any of it" - ), - new RegExp( - "CodeQL did not detect any code written in languages supported by CodeQL" - ) - ] - }, - ["NoSupportedBuildCommandSucceeded" /* NoSupportedBuildCommandSucceeded */]: { - cliErrorMessageCandidates: [ - new RegExp("No supported build command succeeded") - ] - }, - ["NoSupportedBuildSystemDetected" /* NoSupportedBuildSystemDetected */]: { - cliErrorMessageCandidates: [ - new RegExp("No supported build system detected") - ] - }, - ["OutOfMemoryOrDisk" /* OutOfMemoryOrDisk */]: { - cliErrorMessageCandidates: [ - new RegExp("CodeQL is out of memory."), - new RegExp("out of disk"), - new RegExp("No space left on device") - ], - additionalErrorMessageToAppend: "For more information, see https://gh.io/troubleshooting-code-scanning/out-of-disk-or-memory" - }, - ["PackCannotBeFound" /* PackCannotBeFound */]: { - cliErrorMessageCandidates: [ - new RegExp( - "Query pack .* cannot be found\\. Check the spelling of the pack\\." - ), - new RegExp( - "is not a .ql file, .qls file, a directory, or a query pack specification." - ) - ] - }, - ["PackMissingAuth" /* PackMissingAuth */]: { - cliErrorMessageCandidates: [ - new RegExp("GitHub Container registry .* 403 Forbidden"), - new RegExp( - "Do you need to specify a token to authenticate to the registry?" - ) - ] - }, - ["SwiftBuildFailed" /* SwiftBuildFailed */]: { - cliErrorMessageCandidates: [ - new RegExp( - "\\[autobuilder/build\\] \\[build-command-failed\\] `autobuild` failed to run the build command" - ) - ] - }, - ["SwiftIncompatibleOs" /* SwiftIncompatibleOs */]: { - cliErrorMessageCandidates: [ - new RegExp("\\[incompatible-os\\]"), - new RegExp("Swift analysis is only supported on macOS") - ] - }, - ["UnsupportedBuildMode" /* UnsupportedBuildMode */]: { - cliErrorMessageCandidates: [ - new RegExp( - "does not support the .* build mode. Please try using one of the following build modes instead" - ) - ] - }, - ["NotFoundInRegistry" /* NotFoundInRegistry */]: { - cliErrorMessageCandidates: [ - new RegExp("'.*' not found in the registry '.*'") - ] + const hasDiffRanges = await prepareDiffInformedAnalysis( + inputs.codeql, + inputs.features, + logger + ); + await applyIncrementalAnalysisSettings( + config, + hasDiffRanges, + inputs.codeql, + logger + ); + if (await isTrapCachingEnabled(features, config.overlayDatabaseMode)) { + const { trapCaches, trapCacheDownloadTime } = await downloadCacheWithTime( + inputs.codeql, + config.languages, + logger + ); + config.trapCaches = trapCaches; + config.trapCacheDownloadTime = trapCacheDownloadTime; } -}; -function getCliConfigCategoryIfExists(cliError) { - for (const [category, configuration] of Object.entries(cliErrorsConfig)) { - if (cliError.exitCode !== void 0 && configuration.exitCode !== void 0 && cliError.exitCode === configuration.exitCode) { - return category; + await setCppTrapCachingEnvironmentVariables(config, logger); + return config; +} +function isLocal(configPath) { + if (configPath.indexOf("./") === 0) { + return true; + } + return configPath.indexOf("@") === -1; +} +function getLocalConfig(logger, configFile, validateConfig) { + if (!fs9.existsSync(configFile)) { + throw new ConfigurationError( + getConfigFileDoesNotExistErrorMessage(configFile) + ); + } + return parseUserConfig( + logger, + configFile, + fs9.readFileSync(configFile, "utf-8"), + validateConfig + ); +} +function getPathToParsedConfigFile(tempDir) { + return path10.join(tempDir, "config"); +} +async function saveConfig(config, logger) { + const configString = JSON.stringify(config); + const configFile = getPathToParsedConfigFile(config.tempDir); + fs9.mkdirSync(path10.dirname(configFile), { recursive: true }); + fs9.writeFileSync(configFile, configString, "utf8"); + logger.debug("Saved config:"); + logger.debug(configString); +} +async function getConfig(tempDir, logger) { + const configFile = getPathToParsedConfigFile(tempDir); + if (!fs9.existsSync(configFile)) { + return void 0; + } + const configString = fs9.readFileSync(configFile, "utf8"); + logger.debug("Loaded config:"); + logger.debug(configString); + const config = JSON.parse(configString); + if (config.version === void 0) { + throw new ConfigurationError( + `Loaded configuration file, but it does not contain the expected 'version' field.` + ); + } + if (config.version !== getActionVersion()) { + throw new ConfigurationError( + `Loaded a configuration file for version '${config.version}', but running version '${getActionVersion()}'` + ); + } + return config; +} +async function generateRegistries(registriesInput, tempDir, logger) { + const registries = parseRegistries(registriesInput); + let registriesAuthTokens; + let qlconfigFile; + if (registries) { + const qlconfig = createRegistriesBlock(registries); + qlconfigFile = path10.join(tempDir, "qlconfig.yml"); + const qlconfigContents = dump(qlconfig); + fs9.writeFileSync(qlconfigFile, qlconfigContents, "utf8"); + logger.debug("Generated qlconfig.yml:"); + logger.debug(qlconfigContents); + registriesAuthTokens = registries.map((registry) => `${registry.url}=${registry.token}`).join(","); + } + if (typeof process.env.CODEQL_REGISTRIES_AUTH === "string") { + logger.debug( + "Using CODEQL_REGISTRIES_AUTH environment variable to authenticate with registries." + ); + } + return { + registriesAuthTokens: ( + // if the user has explicitly set the CODEQL_REGISTRIES_AUTH env var then use that + process.env.CODEQL_REGISTRIES_AUTH ?? registriesAuthTokens + ), + qlconfigFile + }; +} +function createRegistriesBlock(registries) { + if (!Array.isArray(registries) || registries.some((r) => !r.url || !r.packages)) { + throw new ConfigurationError( + "Invalid 'registries' input. Must be an array of objects with 'url' and 'packages' properties." + ); + } + const safeRegistries = registries.map((registry) => ({ + // ensure the url ends with a slash to avoid a bug in the CLI 2.10.4 + url: !registry?.url.endsWith("/") ? `${registry.url}/` : registry.url, + packages: registry.packages, + kind: registry.kind + })); + const qlconfig = { + registries: safeRegistries + }; + return qlconfig; +} +async function wrapEnvironment(env, operation) { + const oldEnv = { ...process.env }; + for (const [key, value] of Object.entries(env)) { + if (value !== void 0) { + process.env[key] = value; } - for (const e of configuration.cliErrorMessageCandidates) { - if (cliError.message.match(e) || cliError.stderr.match(e)) { - return category; - } + } + try { + await operation(); + } finally { + for (const [key, value] of Object.entries(oldEnv)) { + process.env[key] = value; } } - return void 0; } -function isUnsupportedPlatform() { - return !SUPPORTED_PLATFORMS.some( - ([platform2, arch2]) => platform2 === process.platform && arch2 === process.arch - ); +async function parseBuildModeInput(input, languages, features, logger) { + if (input === void 0) { + return void 0; + } + if (!Object.values(BuildMode).includes(input)) { + throw new ConfigurationError( + `Invalid build mode: '${input}'. Supported build modes are: ${Object.values( + BuildMode + ).join(", ")}.` + ); + } + if (languages.includes("csharp" /* csharp */) && await features.getValue("disable_csharp_buildless" /* DisableCsharpBuildless */)) { + logger.warning( + "Scanning C# code without a build is temporarily unavailable. Falling back to 'autobuild' build mode." + ); + return "autobuild" /* Autobuild */; + } + if (languages.includes("java" /* java */) && await features.getValue("disable_java_buildless_enabled" /* DisableJavaBuildlessEnabled */)) { + logger.warning( + "Scanning Java code without a build is temporarily unavailable. Falling back to 'autobuild' build mode." + ); + return "autobuild" /* Autobuild */; + } + return input; } -function getUnsupportedPlatformError(cliError) { - return new ConfigurationError( - `The CodeQL CLI does not support the platform/architecture combination of ${process.platform}/${process.arch} (see ${"https://codeql.github.com/docs/codeql-overview/system-requirements/" /* SYSTEM_REQUIREMENTS */}). The underlying error was: ${cliError.message}` - ); +function appendExtraQueryExclusions(extraQueryExclusions, cliConfig) { + const augmentedConfig = cloneObject(cliConfig); + if (extraQueryExclusions.length === 0) { + return augmentedConfig; + } + augmentedConfig["query-filters"] = [ + // Ordering matters. If the first filter is an inclusion, it implicitly + // excludes all queries that are not included. If it is an exclusion, + // it implicitly includes all queries that are not excluded. So user + // filters (if any) should always be first to preserve intent. + ...augmentedConfig["query-filters"] || [], + ...extraQueryExclusions + ]; + if (augmentedConfig["query-filters"]?.length === 0) { + delete augmentedConfig["query-filters"]; + } + return augmentedConfig; } -function wrapCliConfigurationError(cliError) { - if (isUnsupportedPlatform()) { - return getUnsupportedPlatformError(cliError); +function isCodeScanningEnabled(config) { + return config.analysisKinds.includes("code-scanning" /* CodeScanning */); +} +function isCodeQualityEnabled(config) { + return config.analysisKinds.includes("code-quality" /* CodeQuality */); +} +function isRiskAssessmentEnabled(config) { + return config.analysisKinds.includes("risk-assessment" /* RiskAssessment */); +} +function getPrimaryAnalysisKind(config) { + if (config.analysisKinds.length === 1) { + return config.analysisKinds[0]; } - const cliConfigErrorCategory = getCliConfigCategoryIfExists(cliError); - if (cliConfigErrorCategory === void 0) { - return cliError; + return isCodeScanningEnabled(config) ? "code-scanning" /* CodeScanning */ : "code-quality" /* CodeQuality */; +} +function getPrimaryAnalysisConfig(config) { + return getAnalysisConfig(getPrimaryAnalysisKind(config)); +} +async function logGitVersionTelemetry(config, gitVersion) { + if (config.languages.length > 0) { + addNoLanguageDiagnostic( + config, + makeTelemetryDiagnostic( + "codeql-action/git-version-telemetry", + "Git version telemetry", + { + fullVersion: gitVersion.fullVersion, + truncatedVersion: gitVersion.truncatedVersion + } + ) + ); } - let errorMessageBuilder = cliError.message; - const additionalErrorMessageToAppend = cliErrorsConfig[cliConfigErrorCategory].additionalErrorMessageToAppend; - if (additionalErrorMessageToAppend !== void 0) { - errorMessageBuilder = `${errorMessageBuilder} ${additionalErrorMessageToAppend}`; +} +async function logGeneratedFilesTelemetry(config, duration, generatedFilesCount) { + if (config.languages.length < 1) { + return; } - return new ConfigurationError(errorMessageBuilder); + addNoLanguageDiagnostic( + config, + makeTelemetryDiagnostic( + "codeql-action/generated-files-telemetry", + "Generated files telemetry", + { + duration, + generatedFilesCount + } + ) + ); } // src/setup-codeql.ts @@ -153439,9 +153538,9 @@ async function initCodeQL(toolsInput, apiDetails, tempDir, variant, defaultCliVe zstdAvailability }; } -async function initConfig2(features, inputs) { +async function initConfig2(actionState, inputs) { return await withGroupAsync("Load language configuration", async () => { - return await initConfig(features, inputs); + return await initConfig(actionState, inputs); }); } async function runDatabaseInitCluster(databaseInitEnvironment, codeql, config, sourceRoot, processName, qlconfigFile, logger) { @@ -156734,7 +156833,7 @@ var import_async = __toESM(require_async(), 1); var import_path6 = require("path"); // node_modules/archiver/lib/error.js -var import_util29 = __toESM(require("util"), 1); +var import_util32 = __toESM(require("util"), 1); var ERROR_CODES = { ABORTED: "archive was aborted", DIRECTORYDIRPATHREQUIRED: "diretory dirpath argument must be a non-empty string value", @@ -156759,7 +156858,7 @@ function ArchiverError(code, data) { this.code = code; this.data = data; } -import_util29.default.inherits(ArchiverError, Error); +import_util32.default.inherits(ArchiverError, Error); // node_modules/archiver/lib/core.js var import_readable_stream2 = __toESM(require_ours(), 1); @@ -159694,36 +159793,6 @@ var github3 = __toESM(require_github()); var io7 = __toESM(require_io()); var semver10 = __toESM(require_semver2()); -// src/config/file.ts -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; -} - // src/workflow.ts var fs27 = __toESM(require("fs")); var path23 = __toESM(require("path")); @@ -160104,8 +160173,9 @@ async function run3(actionState) { logger.info(`Job run UUID is ${jobRunUuid}.`); 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( - { ...actionState, features }, + actionStateWithFeatures, repositoryProperties ); sourceRoot = path24.resolve( @@ -160175,7 +160245,7 @@ async function run3(actionState) { features, repositoryProperties ); - config = await initConfig2(features, { + config = await initConfig2(actionStateWithFeatures, { analysisKinds, languagesInput: getOptionalInput("languages"), queriesInput: getOptionalInput("queries"), diff --git a/src/config-utils.test.ts b/src/config-utils.test.ts index 27de780ad5..1b042480ba 100644 --- a/src/config-utils.test.ts +++ b/src/config-utils.test.ts @@ -36,6 +36,7 @@ import { mockCodeQLVersion, createTestConfig, makeMacro, + initAllState, } from "./testing-utils"; import { GitHubVariant, @@ -160,8 +161,9 @@ test.serial("load empty config", async (t) => { }, }); + const state = initAllState({ logger }); const config = await configUtils.initConfig( - createFeatures([]), + state, createTestInitConfigInputs({ languagesInput: languages, repository: { owner: "github", repo: "example" }, @@ -202,8 +204,9 @@ test.serial("load code quality config", async (t) => { }, }); + const state = initAllState({ logger }); const config = await configUtils.initConfig( - createFeatures([]), + state, createTestInitConfigInputs({ analysisKinds: [AnalysisKind.CodeQuality], languagesInput: languages, @@ -280,9 +283,10 @@ test.serial( repositoryProperties, }); + const state = initAllState({ logger }); await t.notThrowsAsync(async () => { const config = await configUtils.initConfig( - createFeatures([]), + state, createTestInitConfigInputs({ analysisKinds: [AnalysisKind.CodeQuality], languagesInput: languages, @@ -321,8 +325,9 @@ test.serial("loading a saved config produces the same config", async (t) => { // Sanity check that getConfig returns undefined before we have called initConfig t.deepEqual(await configUtils.getConfig(tempDir, logger), undefined); + const state = initAllState({ logger }); const config1 = await configUtils.initConfig( - createFeatures([]), + state, createTestInitConfigInputs({ languagesInput: "javascript,python", tempDir, @@ -373,8 +378,9 @@ test.serial("loading config with version mismatch throws", async (t) => { .stub(actionsUtil, "getActionVersion") .returns("does-not-exist"); + const state = initAllState({ logger }); const config = await configUtils.initConfig( - createFeatures([]), + state, createTestInitConfigInputs({ languagesInput: "javascript,python", tempDir, @@ -402,8 +408,9 @@ test.serial("loading config with version mismatch throws", async (t) => { test.serial("load input outside of workspace", async (t) => { return await withTmpDir(async (tempDir) => { try { + const state = initAllState(); await configUtils.initConfig( - createFeatures([]), + state, createTestInitConfigInputs({ configFile: "../input", tempDir, @@ -424,34 +431,6 @@ test.serial("load input outside of workspace", async (t) => { }); }); -test.serial("load non-local input with invalid repo syntax", async (t) => { - return await withTmpDir(async (tempDir) => { - // no filename given, just a repo - const configFile = "octo-org/codeql-config@main"; - - try { - await configUtils.initConfig( - createFeatures([]), - createTestInitConfigInputs({ - configFile, - tempDir, - workspacePath: tempDir, - }), - ); - throw new Error("initConfig did not throw error"); - } catch (err) { - t.deepEqual( - err, - new ConfigurationError( - errorMessages.getConfigFileRepoFormatInvalidMessage( - "octo-org/codeql-config@main", - ), - ), - ); - } - }); -}); - test.serial("load non-existent input", async (t) => { return await withTmpDir(async (tempDir) => { const languagesInput = "javascript"; @@ -459,8 +438,9 @@ test.serial("load non-existent input", async (t) => { t.false(fs.existsSync(path.join(tempDir, configFile))); try { + const state = initAllState(); await configUtils.initConfig( - createFeatures([]), + state, createTestInitConfigInputs({ languagesInput, configFile, @@ -536,8 +516,9 @@ test.serial("load non-empty input", async (t) => { const languagesInput = "javascript"; const configFilePath = createConfigFile(inputFileContents, tempDir); + const state = initAllState(); const actualConfig = await configUtils.initConfig( - createFeatures([]), + state, createTestInitConfigInputs({ languagesInput, buildModeInput: "none", @@ -595,8 +576,9 @@ test.serial( // Only JS, python packs will be ignored const languagesInput = "javascript"; + const state = initAllState(); const config = await configUtils.initConfig( - createFeatures([]), + state, createTestInitConfigInputs({ languagesInput, configFile: configFilePath, @@ -647,8 +629,9 @@ test.serial("API client used when reading remote config", async (t) => { const configFile = "octo-org/codeql-config/config.yaml@main"; const languagesInput = "javascript"; + const state = initAllState(); await configUtils.initConfig( - createFeatures([]), + state, createTestInitConfigInputs({ languagesInput, configFile, @@ -669,9 +652,10 @@ test.serial( mockGetContents(dummyResponse); const repoReference = "octo-org/codeql-config/config.yaml@main"; + const state = initAllState(); try { await configUtils.initConfig( - createFeatures([]), + state, createTestInitConfigInputs({ configFile: repoReference, tempDir, @@ -699,9 +683,10 @@ test.serial("Invalid format of remote config handled correctly", async (t) => { mockGetContents(dummyResponse); const repoReference = "octo-org/codeql-config/config.yaml@main"; + const state = initAllState(); try { await configUtils.initConfig( - createFeatures([]), + state, createTestInitConfigInputs({ configFile: repoReference, tempDir, @@ -729,9 +714,10 @@ test.serial("No detected languages", async (t) => { }, }); + const state = initAllState(); try { await configUtils.initConfig( - createFeatures([]), + state, createTestInitConfigInputs({ tempDir, codeql, @@ -752,9 +738,10 @@ test.serial("Unknown languages", async (t) => { return await withTmpDir(async (tempDir) => { const languagesInput = "rubbish,english"; + const state = initAllState(); try { await configUtils.initConfig( - createFeatures([]), + state, createTestInitConfigInputs({ languagesInput, tempDir, diff --git a/src/config-utils.ts b/src/config-utils.ts index 972734877a..747fd19a83 100644 --- a/src/config-utils.ts +++ b/src/config-utils.ts @@ -5,6 +5,7 @@ import { performance } from "perf_hooks"; import * as core from "@actions/core"; import * as yaml from "js-yaml"; +import { ActionState } from "./action-common"; import { getActionVersion, getOptionalInput, @@ -18,8 +19,9 @@ import { getAnalysisConfig, } from "./analyses"; import * as api from "./api-client"; -import { CachingKind, getCachingKind } from "./caching-utils"; +import { getCachingKind } from "./caching-utils"; import { type CodeQL } from "./codeql"; +import { type Config } from "./config/action-config"; import { calculateAugmentation, ExcludeQueryFilter, @@ -27,6 +29,12 @@ import { parseUserConfig, UserConfig, } from "./config/db-config"; +import { getRemoteConfig } from "./config/file"; +import { + parseRegistries, + type RegistryConfigNoCredentials, + type RegistryConfigWithCredentials, +} from "./config/pack-registries"; import { addNoLanguageDiagnostic, makeTelemetryDiagnostic, @@ -79,6 +87,8 @@ import { isHostedRunner, } from "./util"; +export { type Config } from "./config/action-config"; + /** * The minimum available disk space (in MB) required to perform overlay analysis. * If the available disk space on the runner is below the threshold when deciding @@ -117,148 +127,6 @@ const OVERLAY_MINIMUM_MEMORY_MB = 5 * 1024; */ const CODEQL_VERSION_REDUCED_OVERLAY_MEMORY_USAGE = "2.24.3"; -export type RegistryConfigWithCredentials = RegistryConfigNoCredentials & { - // Token to use when downloading packs from this registry. - token: string; -}; - -/** - * The list of registries and the associated pack globs that determine where each - * pack can be downloaded from. - */ -export interface RegistryConfigNoCredentials { - // URL of a package registry, eg- https://ghcr.io/v2/ - url: string; - - // List of globs that determine which packs are associated with this registry. - packages: string[] | string; - - // Kind of registry, either "github" or "docker". Default is "docker". - // "docker" refers specifically to the GitHub Container Registry, which is the usual way of sharing CodeQL packs. - // "github" refers to packs published as content in a GitHub repository. This kind of registry is used in scenarios - // where GHCR is not available, such as certain GHES environments. - kind?: "github" | "docker"; -} - -/** - * Format of the parsed config file. - */ -export interface Config { - /** - * The version of the CodeQL Action that the configuration is for. - */ - version: string; - /** - * Set of analysis kinds that are enabled. - */ - analysisKinds: AnalysisKind[]; - /** - * Set of languages to run analysis for. - */ - languages: Language[]; - /** - * Build mode, if set. Currently only a single build mode is supported per job. - */ - buildMode: BuildMode | undefined; - /** - * A unaltered copy of the original user input. - * Mainly intended to be used for status reporting. - * If any field is useful for the actual processing - * of the action then consider pulling it out to a - * top-level field above. - */ - originalUserInput: UserConfig; - /** - * Directory to use for temporary files that should be - * deleted at the end of the job. - */ - tempDir: string; - /** - * Path of the CodeQL executable. - */ - codeQLCmd: string; - /** - * Version of GitHub we are talking to. - */ - gitHubVersion: GitHubVersion; - /** - * The location where CodeQL databases should be stored. - */ - dbLocation: string; - /** - * Specifies whether we are debugging mode and should try to produce extra - * output for debugging purposes when possible. - */ - debugMode: boolean; - /** - * Specifies the name of the debugging artifact if we are in debug mode. - */ - debugArtifactName: string; - /** - * Specifies the name of the database in the debugging artifact. - */ - debugDatabaseName: string; - /** - * The configuration we computed by combining `originalUserInput` with `augmentationProperties`, - * as well as adjustments made to it based on unsupported or required options. - */ - computedConfig: UserConfig; - - /** - * Partial map from languages to locations of TRAP caches for that language. - * If a key is omitted, then TRAP caching should not be used for that language. - */ - trapCaches: { [language: Language]: string }; - - /** - * Time taken to download TRAP caches. Used for status reporting. - */ - trapCacheDownloadTime: number; - - /** A value indicating how dependency caching should be used. */ - dependencyCachingEnabled: CachingKind; - - /** The keys of caches that we restored, if any. */ - dependencyCachingRestoredKeys: string[]; - - /** - * Extra query exclusions to append to the config. - */ - extraQueryExclusions: ExcludeQueryFilter[]; - - /** - * The overlay database mode to use. - */ - overlayDatabaseMode: OverlayDatabaseMode; - - /** - * Whether to use caching for overlay databases. If it is true, the action - * will upload the created overlay-base database to the actions cache, and - * download an overlay-base database from the actions cache before it creates - * a new overlay database. If it is false, the action assumes that the - * workflow will be responsible for managing database storage and retrieval. - * - * This property has no effect unless `overlayDatabaseMode` is `Overlay` or - * `OverlayBase`. - */ - useOverlayDatabaseCaching: boolean; - - /** - * Whether the overlay database mode was set explicitly. - */ - overlayModeSetExplicitly: boolean; - - /** - * A partial mapping from repository properties that affect us to their values. - */ - repositoryProperties: RepositoryProperties; - - /** - * Whether to enable file coverage information. - */ - enableFileCoverageInformation: boolean; -} - async function getSupportedLanguageMap( codeql: CodeQL, logger: Logger, @@ -599,12 +467,11 @@ async function downloadCacheWithTime( } async function loadUserConfig( - logger: Logger, + actionState: ActionState<["Logger", "Env", "FeatureFlags"]>, configFile: string, workspacePath: string, apiDetails: api.GitHubApiCombinedDetails, tempDir: string, - validateConfig: boolean, ): Promise { if (isLocal(configFile)) { if (configFile !== userConfigFromActionPath(tempDir)) { @@ -617,14 +484,12 @@ async function loadUserConfig( ); } } - return getLocalConfig(logger, configFile, validateConfig); - } else { - return await getRemoteConfig( - logger, - configFile, - apiDetails, - validateConfig, + const validateConfig = await actionState.features.getValue( + Feature.ValidateDbConfig, ); + return getLocalConfig(actionState.logger, configFile, validateConfig); + } else { + return await getRemoteConfig(actionState, configFile, apiDetails); } } @@ -1138,10 +1003,11 @@ export async function applyIncrementalAnalysisSettings( * a default config. The parsed config is then stored to a known location. */ export async function initConfig( - features: FeatureEnablement, + actionState: ActionState<["Logger", "Env", "FeatureFlags"]>, inputs: InitConfigInputs, ): Promise { - const { logger, tempDir } = inputs; + const { logger, features } = actionState; + const { tempDir } = inputs; // if configInput is set, it takes precedence over configFile if (inputs.configInput) { @@ -1160,14 +1026,12 @@ export async function initConfig( logger.debug("No configuration file was provided"); } else { logger.debug(`Using configuration file: ${inputs.configFile}`); - const validateConfig = await features.getValue(Feature.ValidateDbConfig); userConfig = await loadUserConfig( - logger, + actionState, inputs.configFile, inputs.workspacePath, inputs.apiDetails, tempDir, - validateConfig, ); } @@ -1317,29 +1181,6 @@ export async function initConfig( return config; } -function parseRegistries( - registriesInput: string | undefined, -): RegistryConfigWithCredentials[] | undefined { - try { - return registriesInput - ? (yaml.load(registriesInput) as RegistryConfigWithCredentials[]) - : undefined; - } catch { - throw new ConfigurationError( - "Invalid registries input. Must be a YAML string.", - ); - } -} - -export function parseRegistriesWithoutCredentials( - registriesInput?: string, -): RegistryConfigNoCredentials[] | undefined { - return parseRegistries(registriesInput)?.map((r) => { - const { url, packages, kind } = r; - return { url, packages, kind }; - }); -} - function isLocal(configPath: string): boolean { // If the path starts with ./, look locally if (configPath.indexOf("./") === 0) { @@ -1369,54 +1210,6 @@ function getLocalConfig( ); } -async function getRemoteConfig( - logger: Logger, - configFile: string, - apiDetails: api.GitHubApiCombinedDetails, - validateConfig: boolean, -): Promise { - // retrieve the various parts of the config location, and ensure they're present - const format = new RegExp( - "(?[^/]+)/(?[^/]+)/(?[^@]+)@(?.*)", - ); - const pieces = format.exec(configFile); - // 5 = 4 groups + the whole expression - if (pieces?.groups === undefined || pieces.length < 5) { - throw new ConfigurationError( - errorMessages.getConfigFileRepoFormatInvalidMessage(configFile), - ); - } - - const response = await api - .getApiClientWithExternalAuth(apiDetails) - .rest.repos.getContent({ - owner: pieces.groups.owner, - repo: pieces.groups.repo, - path: pieces.groups.path, - ref: pieces.groups.ref, - }); - - let fileContents: string; - if ("content" in response.data && response.data.content !== undefined) { - fileContents = response.data.content; - } else if (Array.isArray(response.data)) { - throw new ConfigurationError( - errorMessages.getConfigFileDirectoryGivenMessage(configFile), - ); - } else { - throw new ConfigurationError( - errorMessages.getConfigFileFormatInvalidMessage(configFile), - ); - } - - return parseUserConfig( - logger, - configFile, - Buffer.from(fileContents, "base64").toString("binary"), - validateConfig, - ); -} - /** * Get the file path where the parsed config will be stored. */ diff --git a/src/config/action-config.ts b/src/config/action-config.ts new file mode 100644 index 0000000000..de6882e77e --- /dev/null +++ b/src/config/action-config.ts @@ -0,0 +1,128 @@ +import type { AnalysisKind } from "../analyses"; +import type { CachingKind } from "../caching-utils"; +import type { RepositoryProperties } from "../feature-flags/properties"; +import type { Language } from "../languages"; +import type { OverlayDatabaseMode } from "../overlay/overlay-database-mode"; +import type { BuildMode, GitHubVersion } from "../util"; + +import type { ExcludeQueryFilter, UserConfig } from "./db-config"; + +/** + * Format of the CodeQL Action configuration state that is persisted + * between steps of the CodeQL Action in a CodeQL workflow. + */ +export interface Config { + /** + * The version of the CodeQL Action that the configuration is for. + */ + version: string; + /** + * Set of analysis kinds that are enabled. + */ + analysisKinds: AnalysisKind[]; + /** + * Set of languages to run analysis for. + */ + languages: Language[]; + /** + * Build mode, if set. Currently only a single build mode is supported per job. + */ + buildMode: BuildMode | undefined; + /** + * A unaltered copy of the original user input. + * Mainly intended to be used for status reporting. + * If any field is useful for the actual processing + * of the action then consider pulling it out to a + * top-level field above. + */ + originalUserInput: UserConfig; + /** + * Directory to use for temporary files that should be + * deleted at the end of the job. + */ + tempDir: string; + /** + * Path of the CodeQL executable. + */ + codeQLCmd: string; + /** + * Version of GitHub we are talking to. + */ + gitHubVersion: GitHubVersion; + /** + * The location where CodeQL databases should be stored. + */ + dbLocation: string; + /** + * Specifies whether we are debugging mode and should try to produce extra + * output for debugging purposes when possible. + */ + debugMode: boolean; + /** + * Specifies the name of the debugging artifact if we are in debug mode. + */ + debugArtifactName: string; + /** + * Specifies the name of the database in the debugging artifact. + */ + debugDatabaseName: string; + /** + * The configuration we computed by combining `originalUserInput` with `augmentationProperties`, + * as well as adjustments made to it based on unsupported or required options. + */ + computedConfig: UserConfig; + + /** + * Partial map from languages to locations of TRAP caches for that language. + * If a key is omitted, then TRAP caching should not be used for that language. + */ + trapCaches: { [language: Language]: string }; + + /** + * Time taken to download TRAP caches. Used for status reporting. + */ + trapCacheDownloadTime: number; + + /** A value indicating how dependency caching should be used. */ + dependencyCachingEnabled: CachingKind; + + /** The keys of caches that we restored, if any. */ + dependencyCachingRestoredKeys: string[]; + + /** + * Extra query exclusions to append to the config. + */ + extraQueryExclusions: ExcludeQueryFilter[]; + + /** + * The overlay database mode to use. + */ + overlayDatabaseMode: OverlayDatabaseMode; + + /** + * Whether to use caching for overlay databases. If it is true, the action + * will upload the created overlay-base database to the actions cache, and + * download an overlay-base database from the actions cache before it creates + * a new overlay database. If it is false, the action assumes that the + * workflow will be responsible for managing database storage and retrieval. + * + * This property has no effect unless `overlayDatabaseMode` is `Overlay` or + * `OverlayBase`. + */ + useOverlayDatabaseCaching: boolean; + + /** + * Whether the overlay database mode was set explicitly. + */ + overlayModeSetExplicitly: boolean; + + /** + * A partial mapping from repository properties that affect us to their values. + */ + repositoryProperties: RepositoryProperties; + + /** + * Whether to enable file coverage information. + */ + enableFileCoverageInformation: boolean; +} diff --git a/src/config/file.ts b/src/config/file.ts index d216d3fda1..5ff2dee082 100644 --- a/src/config/file.ts +++ b/src/config/file.ts @@ -1,9 +1,15 @@ import { ActionState } from "../action-common"; +import * as api from "../api-client"; +import * as errorMessages from "../error-messages"; import { Feature } from "../feature-flags"; import { RepositoryProperties, RepositoryPropertyName, } from "../feature-flags/properties"; +import { ConfigurationError } from "../util"; + +import { parseUserConfig, UserConfig } from "./db-config"; +import { parseRemoteFileAddress } from "./remote-file"; /** * Gets the value that is configured for the configuration file, if any. @@ -46,3 +52,52 @@ export async function getConfigFileInput( return undefined; } + +/** + * Attempts to fetch a `UserConfig` from a remote `address`. + * + * @param actionState The current Action state. + * @param configFile The remote address of the configuration file. + * @param apiDetails Information about how to connect to the API. + * + * @returns The `UserConfig`, if it could be fetched and parsed successfully. + */ +export async function getRemoteConfig( + actionState: ActionState<["Logger", "Env", "FeatureFlags"]>, + configFile: string, + apiDetails: api.GitHubApiCombinedDetails, +): Promise { + const address = await parseRemoteFileAddress(actionState, configFile); + + const response = await api + .getApiClientWithExternalAuth(apiDetails) + .rest.repos.getContent({ + owner: address.owner, + repo: address.repo, + path: address.path, + ref: address.ref, + }); + + let fileContents: string; + if ("content" in response.data && response.data.content !== undefined) { + fileContents = response.data.content; + } else if (Array.isArray(response.data)) { + throw new ConfigurationError( + errorMessages.getConfigFileDirectoryGivenMessage(configFile), + ); + } else { + throw new ConfigurationError( + errorMessages.getConfigFileFormatInvalidMessage(configFile), + ); + } + + const validateConfig = await actionState.features.getValue( + Feature.ValidateDbConfig, + ); + return parseUserConfig( + actionState.logger, + configFile, + Buffer.from(fileContents, "base64").toString("binary"), + validateConfig, + ); +} diff --git a/src/config/pack-registries.ts b/src/config/pack-registries.ts new file mode 100644 index 0000000000..76d2f6cd47 --- /dev/null +++ b/src/config/pack-registries.ts @@ -0,0 +1,49 @@ +import * as yaml from "js-yaml"; + +import { ConfigurationError } from "../util"; + +export type RegistryConfigWithCredentials = RegistryConfigNoCredentials & { + // Token to use when downloading packs from this registry. + token: string; +}; + +/** + * The list of registries and the associated pack globs that determine where each + * pack can be downloaded from. + */ +export interface RegistryConfigNoCredentials { + // URL of a package registry, eg- https://ghcr.io/v2/ + url: string; + + // List of globs that determine which packs are associated with this registry. + packages: string[] | string; + + // Kind of registry, either "github" or "docker". Default is "docker". + // "docker" refers specifically to the GitHub Container Registry, which is the usual way of sharing CodeQL packs. + // "github" refers to packs published as content in a GitHub repository. This kind of registry is used in scenarios + // where GHCR is not available, such as certain GHES environments. + kind?: "github" | "docker"; +} + +export function parseRegistries( + registriesInput: string | undefined, +): RegistryConfigWithCredentials[] | undefined { + try { + return registriesInput + ? (yaml.load(registriesInput) as RegistryConfigWithCredentials[]) + : undefined; + } catch { + throw new ConfigurationError( + "Invalid registries input. Must be a YAML string.", + ); + } +} + +export function parseRegistriesWithoutCredentials( + registriesInput?: string, +): RegistryConfigNoCredentials[] | undefined { + return parseRegistries(registriesInput)?.map((r) => { + const { url, packages, kind } = r; + return { url, packages, kind }; + }); +} diff --git a/src/config/remote-file.test.ts b/src/config/remote-file.test.ts new file mode 100644 index 0000000000..c366370293 --- /dev/null +++ b/src/config/remote-file.test.ts @@ -0,0 +1,282 @@ +import test from "ava"; +import sinon from "sinon"; + +import { ActionsEnvVars } from "../actions-util"; +import * as errors from "../error-messages"; +import { Feature } from "../feature-flags"; +import { callee, getTestEnv } from "../testing-utils"; +import { ConfigurationError } from "../util"; + +import { + DEFAULT_CONFIG_FILE_NAME, + DEFAULT_CONFIG_FILE_REF, + parseRemoteFileAddress, + RemoteFileAddress, +} from "./remote-file"; + +type ParseRemoteFileAddressTest = { + input: string; + expected: RemoteFileAddress; +}; + +test("parseRemoteFileAddress accepts full remote addresses", async (t) => { + const target = callee(parseRemoteFileAddress); + + const expected: RemoteFileAddress = { + owner: "owner", + repo: "repo", + path: "path", + ref: "ref", + }; + + const oldFormatInputs: ParseRemoteFileAddressTest[] = [ + { input: "owner/repo/path@ref", expected }, + { input: "owner /repo/path@ref", expected }, + { input: "owner/ repo/path@ref", expected }, + { input: "owner/repo /path@ref", expected }, + { input: "owner/repo/ path@ref", expected }, + { input: "owner/repo/path @ref", expected }, + { input: "owner/repo/path@ ref", expected }, + { + input: "owner/repo/path/to/codeql.yml@ref/feature", + expected: { ...expected, path: "path/to/codeql.yml", ref: "ref/feature" }, + }, + { + input: " owner/repo/path/to/codeql.yml@ref/feature ", + expected: { ...expected, path: "path/to/codeql.yml", ref: "ref/feature" }, + }, + ]; + + for (const oldFormatInput of oldFormatInputs) { + await target + .withArgs(oldFormatInput.input) + .passes(async (fn) => t.deepEqual(await fn(), oldFormatInput.expected)); + } + + // New format. + const newFormatInputs: ParseRemoteFileAddressTest[] = [ + { input: "owner/repo@ref:path", expected }, + { input: "owner /repo@ref:path", expected }, + { input: "owner/ repo@ref:path", expected }, + { input: "owner/repo @ref:path", expected }, + { input: "owner/repo@ ref:path", expected }, + { input: "owner/repo@ref :path", expected }, + { input: "owner/repo@ref: path", expected }, + { + input: "owner/repo@ref/feature:path/to/codeql.yml", + expected: { ...expected, path: "path/to/codeql.yml", ref: "ref/feature" }, + }, + { + input: " owner/repo@ref/feature:path/to/codeql.yml ", + expected: { ...expected, path: "path/to/codeql.yml", ref: "ref/feature" }, + }, + ]; + + for (const newFormatInput of newFormatInputs) { + const targetWithArgs = target.withArgs(newFormatInput.input); + + // Should fail when the FF is not enabled. + await targetWithArgs + .withFeatures([]) + .passes(async (fn) => + t.throwsAsync(fn, { instanceOf: ConfigurationError }), + ); + + // And pass when the FF is enabled. + await targetWithArgs + .withFeatures([Feature.NewRemoteFileAddresses]) + .passes(async (fn) => t.deepEqual(await fn(), newFormatInput.expected)); + } +}); + +test("parseRemoteFileAddress accepts remote address without an owner", async (t) => { + const target = callee(parseRemoteFileAddress); + + const env = target.getState().env; + const owner = "test-owner"; + const getRequired = sinon.stub(env, "getRequired"); + getRequired + .withArgs(ActionsEnvVars.GITHUB_REPOSITORY) + .returns(`${owner}/current-repo`); + + const targetWithEnv = target.withEnv(env); + + const testCases: ParseRemoteFileAddressTest[] = [ + { + input: "repo@ref:path.yml", + expected: { + owner, + repo: "repo", + path: "path.yml", + ref: "ref", + }, + }, + { + input: "repo@ref", + expected: { + owner, + repo: "repo", + path: DEFAULT_CONFIG_FILE_NAME, + ref: "ref", + }, + }, + { + input: "repo:path.yml", + expected: { + owner, + repo: "repo", + path: "path.yml", + ref: DEFAULT_CONFIG_FILE_REF, + }, + }, + { + input: "repo", + expected: { + owner, + repo: "repo", + path: DEFAULT_CONFIG_FILE_NAME, + ref: DEFAULT_CONFIG_FILE_REF, + }, + }, + ]; + + for (const testCase of testCases) { + const targetWithArgs = targetWithEnv.withArgs(testCase.input); + + // Should fail when the FF is not enabled. + await targetWithArgs + .withFeatures([]) + .passes(async (fn) => + t.throwsAsync(fn, { instanceOf: ConfigurationError }), + ); + + // And pass when the FF is enabled. + await targetWithArgs + .withFeatures([Feature.NewRemoteFileAddresses]) + .passes(async (fn) => t.deepEqual(await fn(), testCase.expected)); + } +}); + +test("parseRemoteFileAddress throws for invalid `GITHUB_REPOSITORY`", async (t) => { + const target = callee(parseRemoteFileAddress).withArgs("repo@ref"); + + const env = target.getState().env; + const getRequired = sinon.stub(env, "getRequired"); + getRequired.withArgs(ActionsEnvVars.GITHUB_REPOSITORY).returns(`not-valid`); + + await target + .withEnv(env) + .withFeatures([Feature.NewRemoteFileAddresses]) + .passes(async (fn) => t.throwsAsync(fn, { instanceOf: Error })); + + t.assert(getRequired.calledOnceWith(ActionsEnvVars.GITHUB_REPOSITORY)); +}); + +test("parseRemoteFileAddress accepts remote address without a path", async (t) => { + const target = callee(parseRemoteFileAddress); + + const testCases: ParseRemoteFileAddressTest[] = [ + { + input: "owner/repo@ref", + expected: { + owner: "owner", + repo: "repo", + path: DEFAULT_CONFIG_FILE_NAME, + ref: "ref", + }, + }, + { + input: "owner/repo", + expected: { + owner: "owner", + repo: "repo", + path: DEFAULT_CONFIG_FILE_NAME, + ref: DEFAULT_CONFIG_FILE_REF, + }, + }, + ]; + + for (const testCase of testCases) { + const targetWithArgs = target.withArgs(testCase.input); + + // Should fail when the FF is not enabled. + await targetWithArgs + .withFeatures([]) + .passes(async (fn) => + t.throwsAsync(fn, { instanceOf: ConfigurationError }), + ); + + // And pass when the FF is enabled. + await targetWithArgs + .withFeatures([Feature.NewRemoteFileAddresses]) + .passes(async (fn) => t.deepEqual(await fn(), testCase.expected)); + } +}); + +test("parseRemoteFileAddress accepts remote address without a ref", async (t) => { + const target = callee(parseRemoteFileAddress).withArgs("owner/repo:path"); + + // Should only accept the input if the FF is enabled. + await target.withFeatures([]).passes(t.throwsAsync); + await target + .withFeatures([Feature.NewRemoteFileAddresses]) + .passes(async (fn) => + t.deepEqual(await fn(), { + owner: "owner", + repo: "repo", + path: "path", + ref: DEFAULT_CONFIG_FILE_REF, + } satisfies RemoteFileAddress), + ); +}); + +test("parseRemoteFileAddress rejects invalid values", async (t) => { + const env = getTestEnv(); + const owner = "owner"; + const getRequired = sinon.stub(env, "getRequired"); + getRequired + .withArgs(ActionsEnvVars.GITHUB_REPOSITORY) + .returns(`${owner}/current-repo`); + + const target = callee(parseRemoteFileAddress).withEnv(env); + + const testInputs = [ + " ", + "repo//absolute", + "repo:/absolute", + "/repo@ref", + " /repo@ref", + "repo@", + "repo:", + "repo/", + "/repo", + ":path", + "@ref", + "@ref:path", + "owner/@ref:path", + "owner/@ref", + "owner/:path", + ]; + + for (const testInput of testInputs) { + const targetWithArgs = target.withArgs(testInput); + + // Should throw both when the new format is and isn't accepted. + await targetWithArgs.withFeatures([]).passes(async (fn) => + t.throwsAsync(fn, { + instanceOf: ConfigurationError, + message: errors.getConfigFileRepoOldFormatInvalidMessage(testInput), + }), + ); + await targetWithArgs + .withFeatures([Feature.NewRemoteFileAddresses]) + .passes(async (fn) => + t.throwsAsync(fn, { + // When the new format is accepted, there are some more specific + // errors in some cases. It is sufficient for us to check that + // an exception is thrown. + instanceOf: ConfigurationError, + }), + ); + } +}); diff --git a/src/config/remote-file.ts b/src/config/remote-file.ts new file mode 100644 index 0000000000..15990ed23f --- /dev/null +++ b/src/config/remote-file.ts @@ -0,0 +1,139 @@ +import { ActionState } from "../action-common"; +import { ActionsEnvVars } from "../actions-util"; +import { Env } from "../environment"; +import * as errorMessages from "../error-messages"; +import { Feature } from "../feature-flags"; +import { ConfigurationError, Failure, Result, Success } from "../util"; + +/** Represents remote file addresses. */ +export interface RemoteFileAddress { + /** The owner of the repository. */ + owner: string; + /** The repository name. */ + repo: string; + /** The path of the file. */ + path: string; + /** The ref of the repository. */ + ref: string; +} + +/** The default file path to use in configuration file shorthands. */ +export const DEFAULT_CONFIG_FILE_NAME = ".github/codeql-action.yaml"; + +/** The default ref to use in configuration file shorthands. */ +export const DEFAULT_CONFIG_FILE_REF = "main"; + +/** Extracts the owner from the `GITHUB_REPOSITORY` environment variable. */ +function getDefaultOwner(env: Env): string { + const currentRepoNwo = env.getRequired(ActionsEnvVars.GITHUB_REPOSITORY); + const nwoParts = currentRepoNwo.split("/"); + + if (nwoParts.length !== 2 || nwoParts[0].trim().length === 0) { + // This shouldn't happen, so we should throw if `GITHUB_REPOSITORY` doesn't match + // our expectations. + throw new Error( + `Expected ${ActionsEnvVars.GITHUB_REPOSITORY} to contain a name with owner, but got '${currentRepoNwo}'.`, + ); + } + + return nwoParts[0].trim(); +} + +/** + * The old remote address format that's always been supported for the `config-file` input. + * All the components are required. Unchanged from the previous implementation. + */ +const OLD_REMOTE_ADDRESS_FORMAT = new RegExp( + "(?[^/]+)/(?[^/]+)/(?[^@]+)@(?.*)", +); + +/** + * Attempts to parse `input` as a `RemoteFileAddress` using the old format. + * + * @param input The input to try and parse. + * @returns A `RemoteFileAddress` value if successful or `undefined` otherwise. + */ +function parseOldRemoteFileAddress( + input: string, +): Result { + const pieces = OLD_REMOTE_ADDRESS_FORMAT.exec(input); + + // 5 = 4 groups + the whole expression + if (pieces?.groups === undefined || pieces.length < 5) { + return new Failure(undefined); + } + + return new Success({ + owner: pieces.groups.owner.trim(), + repo: pieces.groups.repo.trim(), + path: pieces.groups.path.trim(), + ref: pieces.groups.ref.trim(), + }); +} + +/** + * Attempts to parse `configFile` into an array of `RemoteFileAddress` components. + * + * @param actionState The current Action state. + * @param configFile The string to try and parse. + * @returns The successful result of executing the regex. + * @throws `ConfigurationError` if the format of `configFile` is not valid. + */ +export async function parseRemoteFileAddress( + actionState: ActionState<["FeatureFlags", "Env"]>, + configFile: string, +): Promise { + // Try to parse the input using the old format. If successful, return the + // resulting `RemoteFileAddress`. Otherwise, continue using the new format. + const oldFormatAddressResult = parseOldRemoteFileAddress(configFile); + + if (oldFormatAddressResult.isSuccess()) { + return oldFormatAddressResult.value; + } + + // If the FF for the new format is not enabled, throw the old format error. + const allowNewFormat = await actionState.features.getValue( + Feature.NewRemoteFileAddresses, + ); + if (!allowNewFormat) { + throw new ConfigurationError( + errorMessages.getConfigFileRepoOldFormatInvalidMessage(configFile), + ); + } + + // retrieve the various parts of the config location, and ensure they're present + const format = new RegExp( + "^((?[^:@/]+)/)?(?[^:@/]+)(@(?[^:]+))?(:(?.+))?$", + ); + const pieces = format.exec(configFile.trim()); + + const repo: string | undefined = pieces?.groups?.repo?.trim(); + + // Check that the regular expression matched and that we have at least the repo name. + if (!pieces?.groups || !repo || repo.length === 0) { + // Neither the old format nor the new format worked. Throw an error that + // explains the format we accept. We only mention the new format, since that's + // what we want to be used going forward. + throw new ConfigurationError( + errorMessages.getConfigFileRepoFormatInvalidMessage(configFile), + ); + } + + const owner: string | undefined = pieces.groups.owner?.trim(); + const path: string | undefined = pieces.groups.path?.trim(); + const ref: string | undefined = pieces.groups.ref?.trim(); + + // Ensure that the path is a relative path. + if (path?.startsWith("/")) { + throw new ConfigurationError( + `The path component of '${configFile}' cannot be an absolute path.`, + ); + } + + return { + owner: owner || getDefaultOwner(actionState.env), + repo, + path: path || DEFAULT_CONFIG_FILE_NAME, + ref: ref || DEFAULT_CONFIG_FILE_REF, + }; +} diff --git a/src/error-messages.ts b/src/error-messages.ts index 578ec69733..bd32a6a04a 100644 --- a/src/error-messages.ts +++ b/src/error-messages.ts @@ -30,7 +30,7 @@ export function getInvalidConfigFileMessage( return `The configuration file "${configFile}" is invalid: ${messages.slice(0, 10).join(", ")}${andMore}`; } -export function getConfigFileRepoFormatInvalidMessage( +export function getConfigFileRepoOldFormatInvalidMessage( configFile: string, ): string { let error = `The configuration file "${configFile}" is not a supported remote file reference.`; @@ -39,6 +39,15 @@ export function getConfigFileRepoFormatInvalidMessage( return error; } +export function getConfigFileRepoFormatInvalidMessage( + configFile: string, +): string { + let error = `The configuration file "${configFile}" is not a supported remote file reference.`; + error += " Expected format [/][@][:]"; + + return error; +} + export function getConfigFileFormatInvalidMessage(configFile: string): string { return `The configuration file "${configFile}" could not be read`; } diff --git a/src/feature-flags.ts b/src/feature-flags.ts index f71ecab57b..dcc5c9d7bd 100644 --- a/src/feature-flags.ts +++ b/src/feature-flags.ts @@ -92,6 +92,8 @@ export enum Feature { ForceNightly = "force_nightly", IgnoreGeneratedFiles = "ignore_generated_files", JavaNetworkDebugging = "java_network_debugging", + /** Allow the new remote file address format. */ + NewRemoteFileAddresses = "new_remote_file_addresses", OverlayAnalysis = "overlay_analysis", OverlayAnalysisCodeScanningCpp = "overlay_analysis_code_scanning_cpp", OverlayAnalysisCodeScanningCsharp = "overlay_analysis_code_scanning_csharp", @@ -255,6 +257,11 @@ export const featureConfig = { envVar: "CODEQL_ACTION_JAVA_NETWORK_DEBUGGING", minimumVersion: undefined, }, + [Feature.NewRemoteFileAddresses]: { + defaultValue: false, + envVar: "CODEQL_ACTION_NEW_REMOTE_FILE_ADDRESSES", + minimumVersion: undefined, + }, [Feature.OverlayAnalysis]: { defaultValue: false, envVar: "CODEQL_ACTION_OVERLAY_ANALYSIS", diff --git a/src/init-action.ts b/src/init-action.ts index c13eb2da65..acaba701be 100644 --- a/src/init-action.ts +++ b/src/init-action.ts @@ -203,7 +203,7 @@ async function sendCompletedStatusReport( } } -async function run(actionState: ActionState<["Logger", "Actions"]>) { +async function run(actionState: ActionState<["Logger", "Env", "Actions"]>) { // To capture errors appropriately, keep as much code within the try-catch as // possible, and only use safe functions outside. @@ -262,8 +262,9 @@ async function run(actionState: ActionState<["Logger", "Actions"]>) { core.exportVariable(EnvVar.INIT_ACTION_HAS_RUN, "true"); + const actionStateWithFeatures = { ...actionState, features }; configFile = await getConfigFileInput( - { ...actionState, features }, + actionStateWithFeatures, repositoryProperties, ); @@ -363,7 +364,7 @@ async function run(actionState: ActionState<["Logger", "Actions"]>) { repositoryProperties, ); - config = await initConfig(features, { + config = await initConfig(actionStateWithFeatures, { analysisKinds, languagesInput: getOptionalInput("languages"), queriesInput: getOptionalInput("queries"), diff --git a/src/init.ts b/src/init.ts index 2533d9a894..53efbe99a3 100644 --- a/src/init.ts +++ b/src/init.ts @@ -7,6 +7,7 @@ import * as github from "@actions/github"; import * as io from "@actions/io"; import * as yaml from "js-yaml"; +import { ActionState } from "./action-common"; import { getOptionalInput, isAnalyzingPullRequest, @@ -81,11 +82,11 @@ export async function initCodeQL( } export async function initConfig( - features: FeatureEnablement, + actionState: ActionState<["Logger", "Env", "FeatureFlags"]>, inputs: configUtils.InitConfigInputs, ): Promise { return await withGroupAsync("Load language configuration", async () => { - return await configUtils.initConfig(features, inputs); + return await configUtils.initConfig(actionState, inputs); }); } diff --git a/src/status-report.ts b/src/status-report.ts index 4a213c933e..a0c0b3ab40 100644 --- a/src/status-report.ts +++ b/src/status-report.ts @@ -12,15 +12,16 @@ import { isSelfHostedRunner, } from "./actions-util"; import { getAnalysisKey, getApiClient } from "./api-client"; -import { parseRegistriesWithoutCredentials, type Config } from "./config-utils"; -import { DependencyCacheRestoreStatusReport } from "./dependency-caching"; +import type { Config } from "./config/action-config"; +import { parseRegistriesWithoutCredentials } from "./config/pack-registries"; +import type { DependencyCacheRestoreStatusReport } from "./dependency-caching"; import { DocUrl } from "./doc-url"; import { EnvVar } from "./environment"; import { getRef } from "./git-utils"; -import { Logger } from "./logging"; -import { OverlayBaseDatabaseDownloadStats } from "./overlay/caching"; +import type { Logger } from "./logging"; +import type { OverlayBaseDatabaseDownloadStats } from "./overlay/caching"; import { getRepositoryNwo } from "./repository"; -import { ToolsSource } from "./setup-codeql"; +import type { ToolsSource } from "./setup-codeql"; import { ConfigurationError, getRequiredEnvParam, diff --git a/src/testing-utils.ts b/src/testing-utils.ts index f7589ca08a..748ce40ea3 100644 --- a/src/testing-utils.ts +++ b/src/testing-utils.ts @@ -193,6 +193,21 @@ export function getTestActionsEnv(): ActionsEnv { /** For testing purposes, we make all available state features accessible in `TestEnv`. */ type AllState = ["Logger", "Env", "Actions", "FeatureFlags"]; +/** Initialise a fresh `ActionState` value. */ +export function initAllState( + overrides?: Partial>, +): ActionState { + return { + name: ActionName.Init, + startedAt: new Date(), + logger: new RecordingLogger(), + env: getTestEnv(), + actions: getTestActionsEnv(), + features: createFeatures([]), + ...overrides, + }; +} + /** * Wraps a function that accepts an `ActionState` for testing in different environments. */ @@ -216,14 +231,7 @@ export class TestEnv< this.state = cloneFrom !== undefined ? { ...cloneFrom.state, logger: this.logger } - : { - name: ActionName.Init, - startedAt: new Date(), - logger: this.logger, - env: getTestEnv(), - actions: getTestActionsEnv(), - features: createFeatures([]), - }; + : initAllState({ logger: this.logger }); } private clone(): TestEnv {