Support auto-detecting Actions workflows

This commit is contained in:
Michael B. Gale
2025-08-07 11:04:21 +01:00
parent e2b6f0f4a3
commit 43638b10a0
6 changed files with 75 additions and 15 deletions

40
lib/config-utils.js generated
View File

@@ -43,6 +43,7 @@ exports.getConfigFileDirectoryGivenMessage = getConfigFileDirectoryGivenMessage;
exports.getNoLanguagesError = getNoLanguagesError;
exports.getUnknownLanguagesError = getUnknownLanguagesError;
exports.getSupportedLanguageMap = getSupportedLanguageMap;
exports.hasActionsWorkflows = hasActionsWorkflows;
exports.getRawLanguagesInRepo = getRawLanguagesInRepo;
exports.getLanguages = getLanguages;
exports.getRawLanguagesNoAutodetect = getRawLanguagesNoAutodetect;
@@ -144,17 +145,40 @@ async function getSupportedLanguageMap(codeql) {
}
return supportedLanguages;
}
const baseWorkflowsPath = ".github/workflows";
/**
* Determines if there exists a `.github/workflows` directory with at least
* one file in it, which we use as an indicator that there are Actions
* workflows in the workspace. This doesn't perfectly detect whether there
* are actually workflows, but should be a good approximation.
*
* Alternatively, we could check specifically for yaml files, or call the
* API to check if it knows about workflows.
*
* @returns True if the non-empty directory exists, false if not.
*/
function hasActionsWorkflows(sourceRoot) {
const workflowsPath = path.resolve(sourceRoot, baseWorkflowsPath);
const stats = fs.lstatSync(workflowsPath);
return stats.isDirectory() && fs.readdirSync(workflowsPath).length > 0;
}
/**
* Gets the set of languages in the current repository.
*/
async function getRawLanguagesInRepo(repository, logger) {
async function getRawLanguagesInRepo(repository, sourceRoot, logger) {
logger.debug(`GitHub repo ${repository.owner} ${repository.repo}`);
const response = await api.getApiClient().rest.repos.listLanguages({
owner: repository.owner,
repo: repository.repo,
});
logger.debug(`Languages API response: ${JSON.stringify(response)}`);
return Object.keys(response.data).map((language) => language.trim().toLowerCase());
const result = Object.keys(response.data).map((language) => language.trim().toLowerCase());
if (hasActionsWorkflows(sourceRoot)) {
logger.debug(`Found a .github/workflows directory`);
result.push("actions");
}
logger.debug(`Raw languages in repository: ${result.join(", ")}`);
return result;
}
/**
* Get the languages to analyse.
@@ -166,9 +190,9 @@ async function getRawLanguagesInRepo(repository, logger) {
* If no languages could be detected from either the workflow or the repository
* then throw an error.
*/
async function getLanguages(codeql, languagesInput, repository, logger) {
async function getLanguages(codeql, languagesInput, repository, sourceRoot, logger) {
// Obtain languages without filtering them.
const { rawLanguages, autodetected } = await getRawLanguages(languagesInput, repository, logger);
const { rawLanguages, autodetected } = await getRawLanguages(languagesInput, repository, sourceRoot, logger);
const languageMap = await getSupportedLanguageMap(codeql);
const languagesSet = new Set();
const unknownLanguages = [];
@@ -215,7 +239,7 @@ function getRawLanguagesNoAutodetect(languagesInput) {
* @returns A tuple containing a list of languages in this repository that might be
* analyzable and whether or not this list was determined automatically.
*/
async function getRawLanguages(languagesInput, repository, logger) {
async function getRawLanguages(languagesInput, repository, sourceRoot, logger) {
// If the user has specified languages, use those.
const languagesFromInput = getRawLanguagesNoAutodetect(languagesInput);
if (languagesFromInput.length > 0) {
@@ -223,15 +247,15 @@ async function getRawLanguages(languagesInput, repository, logger) {
}
// Otherwise, autodetect languages in the repository.
return {
rawLanguages: await getRawLanguagesInRepo(repository, logger),
rawLanguages: await getRawLanguagesInRepo(repository, sourceRoot, logger),
autodetected: true,
};
}
/**
* Get the default config, populated without user configuration file.
*/
async function getDefaultConfig({ languagesInput, queriesInput, qualityQueriesInput, packsInput, buildModeInput, dbLocation, trapCachingEnabled, dependencyCachingEnabled, debugMode, debugArtifactName, debugDatabaseName, repository, tempDir, codeql, githubVersion, features, logger, }) {
const languages = await getLanguages(codeql, languagesInput, repository, logger);
async function getDefaultConfig({ languagesInput, queriesInput, qualityQueriesInput, packsInput, buildModeInput, dbLocation, trapCachingEnabled, dependencyCachingEnabled, debugMode, debugArtifactName, debugDatabaseName, repository, tempDir, codeql, sourceRoot, githubVersion, features, logger, }) {
const languages = await getLanguages(codeql, languagesInput, repository, sourceRoot, logger);
const buildMode = await parseBuildModeInput(buildModeInput, languages, features, logger);
const augmentationProperties = await calculateAugmentation(packsInput, queriesInput, qualityQueriesInput, languages);
const { trapCaches, trapCacheDownloadTime } = await downloadCacheWithTime(trapCachingEnabled, codeql, languages, logger);