2020-04-28 16:46:47 +02:00
"use strict" ;
2021-07-27 17:59:59 +01:00
var _ _createBinding = ( this && this . _ _createBinding ) || ( Object . create ? ( function ( o , m , k , k2 ) {
if ( k2 === undefined ) k2 = k ;
2023-01-18 20:00:33 +00:00
var desc = Object . getOwnPropertyDescriptor ( m , k ) ;
if ( ! desc || ( "get" in desc ? ! m . _ _esModule : desc . writable || desc . configurable ) ) {
desc = { enumerable : true , get : function ( ) { return m [ k ] ; } } ;
}
Object . defineProperty ( o , k2 , desc ) ;
2021-07-27 17:59:59 +01:00
} ) : ( function ( o , m , k , k2 ) {
if ( k2 === undefined ) k2 = k ;
o [ k2 ] = m [ k ] ;
} ) ) ;
var _ _setModuleDefault = ( this && this . _ _setModuleDefault ) || ( Object . create ? ( function ( o , v ) {
Object . defineProperty ( o , "default" , { enumerable : true , value : v } ) ;
} ) : function ( o , v ) {
o [ "default" ] = v ;
} ) ;
2020-04-28 16:46:47 +02:00
var _ _importStar = ( this && this . _ _importStar ) || function ( mod ) {
if ( mod && mod . _ _esModule ) return mod ;
var result = { } ;
2021-07-27 17:59:59 +01:00
if ( mod != null ) for ( var k in mod ) if ( k !== "default" && Object . prototype . hasOwnProperty . call ( mod , k ) ) _ _createBinding ( result , mod , k ) ;
_ _setModuleDefault ( result , mod ) ;
2020-04-28 16:46:47 +02:00
return result ;
} ;
Object . defineProperty ( exports , "__esModule" , { value : true } ) ;
2024-02-12 22:47:05 +00:00
exports . parseBuildModeInput = exports . wrapEnvironment = exports . generateRegistries = exports . getConfig = exports . getPathToParsedConfigFile = exports . initConfig = exports . validatePackSpecification = exports . parsePacksSpecification = exports . parsePacksFromInput = exports . calculateAugmentation = exports . getDefaultConfig = exports . getRawLanguages = exports . getLanguageAliases = exports . getLanguages = exports . getLanguagesInRepo = exports . getUnknownLanguagesError = exports . getNoLanguagesError = exports . getConfigFileDirectoryGivenMessage = exports . getConfigFileFormatInvalidMessage = exports . getConfigFileRepoFormatInvalidMessage = exports . getConfigFileDoesNotExistErrorMessage = exports . getConfigFileOutsideWorkspaceErrorMessage = exports . getPacksStrInvalid = exports . defaultAugmentationProperties = exports . BuildMode = void 0 ;
2020-09-29 14:43:37 +01:00
const fs = _ _importStar ( require ( "fs" ) ) ;
const path = _ _importStar ( require ( "path" ) ) ;
2022-08-19 11:12:10 +01:00
const perf _hooks _1 = require ( "perf_hooks" ) ;
2020-09-29 14:43:37 +01:00
const yaml = _ _importStar ( require ( "js-yaml" ) ) ;
2021-06-03 09:32:44 -07:00
const semver = _ _importStar ( require ( "semver" ) ) ;
2020-10-01 11:03:30 +01:00
const api = _ _importStar ( require ( "./api-client" ) ) ;
2022-01-06 11:55:03 +00:00
const codeql _1 = require ( "./codeql" ) ;
2024-02-12 22:47:05 +00:00
const feature _flags _1 = require ( "./feature-flags" ) ;
2020-08-10 11:54:02 +01:00
const languages _1 = require ( "./languages" ) ;
2022-08-02 17:52:22 +01:00
const trap _caching _1 = require ( "./trap-caching" ) ;
2022-01-06 11:55:03 +00:00
const util _1 = require ( "./util" ) ;
2020-06-26 15:33:59 +01:00
// Property names from the user-supplied config file.
2021-06-03 09:32:44 -07:00
const PACKS _PROPERTY = "packs" ;
2024-02-02 19:13:42 +00:00
var BuildMode ;
( function ( BuildMode ) {
BuildMode [ "None" ] = "none" ;
BuildMode [ "Autobuild" ] = "autobuild" ;
BuildMode [ "Manual" ] = "manual" ;
} ) ( BuildMode || ( exports . BuildMode = BuildMode = { } ) ) ;
2022-08-10 15:42:45 -07:00
/**
2023-10-02 17:20:50 +01:00
* The default, empty augmentation properties. This is most useful
2022-08-10 15:42:45 -07:00
* for tests.
*/
exports . defaultAugmentationProperties = {
queriesInputCombines : false ,
packsInputCombines : false ,
2022-08-11 09:56:08 -07:00
packsInput : undefined ,
queriesInput : undefined ,
2022-08-10 15:42:45 -07:00
} ;
2021-06-03 09:32:44 -07:00
function getPacksStrInvalid ( packStr , configFile ) {
2021-06-23 15:41:52 -07:00
return configFile
? getConfigFilePropertyError ( configFile , PACKS _PROPERTY , ` " ${ packStr } " is not a valid pack ` )
: ` " ${ packStr } " is not a valid pack ` ;
2021-06-03 09:32:44 -07:00
}
exports . getPacksStrInvalid = getPacksStrInvalid ;
2020-05-05 17:32:58 +01:00
function getConfigFileOutsideWorkspaceErrorMessage ( configFile ) {
2020-09-14 10:44:43 +01:00
return ` The configuration file " ${ configFile } " is outside of the workspace ` ;
2020-05-05 17:32:58 +01:00
}
exports . getConfigFileOutsideWorkspaceErrorMessage = getConfigFileOutsideWorkspaceErrorMessage ;
function getConfigFileDoesNotExistErrorMessage ( configFile ) {
2020-09-14 10:44:43 +01:00
return ` The configuration file " ${ configFile } " does not exist ` ;
2020-05-05 17:32:58 +01:00
}
exports . getConfigFileDoesNotExistErrorMessage = getConfigFileDoesNotExistErrorMessage ;
2020-06-26 15:44:57 +01:00
function getConfigFileRepoFormatInvalidMessage ( configFile ) {
2020-09-14 10:44:43 +01:00
let error = ` The configuration file " ${ configFile } " is not a supported remote file reference. ` ;
error += " Expected format <owner>/<repository>/<file-path>@<ref>" ;
2020-06-25 13:21:46 +01:00
return error ;
}
2020-06-26 15:44:57 +01:00
exports . getConfigFileRepoFormatInvalidMessage = getConfigFileRepoFormatInvalidMessage ;
2020-06-26 15:56:33 +01:00
function getConfigFileFormatInvalidMessage ( configFile ) {
2020-09-14 10:44:43 +01:00
return ` The configuration file " ${ configFile } " could not be read ` ;
2020-06-26 15:56:33 +01:00
}
exports . getConfigFileFormatInvalidMessage = getConfigFileFormatInvalidMessage ;
function getConfigFileDirectoryGivenMessage ( configFile ) {
2020-09-14 10:44:43 +01:00
return ` The configuration file " ${ configFile } " looks like a directory, not a file ` ;
2020-06-26 15:56:33 +01:00
}
exports . getConfigFileDirectoryGivenMessage = getConfigFileDirectoryGivenMessage ;
2020-05-26 19:23:28 +01:00
function getConfigFilePropertyError ( configFile , property , error ) {
2020-08-24 16:44:59 +01:00
if ( configFile === undefined ) {
2020-09-14 10:44:43 +01:00
return ` The workflow property " ${ property } " is invalid: ${ error } ` ;
2020-08-24 16:44:59 +01:00
}
else {
2020-09-14 10:44:43 +01:00
return ` The configuration file " ${ configFile } " is invalid: property " ${ property } " ${ error } ` ;
2020-08-24 16:44:59 +01:00
}
2020-05-26 19:23:28 +01:00
}
2020-08-06 17:45:42 +01:00
function getNoLanguagesError ( ) {
2020-09-14 10:44:43 +01:00
return ( "Did not detect any languages to analyze. " +
"Please update input in workflow or check that GitHub detects the correct languages in your repository." ) ;
2020-08-06 17:45:42 +01:00
}
exports . getNoLanguagesError = getNoLanguagesError ;
function getUnknownLanguagesError ( languages ) {
2023-09-14 15:26:38 +01:00
return ` Did not recognize the following languages: ${ languages . join ( ", " ) } ` ;
2020-08-06 17:45:42 +01:00
}
exports . getUnknownLanguagesError = getUnknownLanguagesError ;
2020-06-26 15:33:59 +01:00
/**
2022-11-23 16:16:02 -08:00
* Gets the set of languages in the current repository that are
* scannable by CodeQL.
2020-06-26 15:33:59 +01:00
*/
2022-11-14 19:48:27 +00:00
async function getLanguagesInRepo ( repository , logger ) {
2020-08-27 10:46:26 +01:00
logger . debug ( ` GitHub repo ${ repository . owner } ${ repository . repo } ` ) ;
2023-07-13 11:17:33 +01:00
const response = await api . getApiClient ( ) . rest . repos . listLanguages ( {
2020-08-27 10:46:26 +01:00
owner : repository . owner ,
2020-09-14 10:44:43 +01:00
repo : repository . repo ,
2020-08-27 10:46:26 +01:00
} ) ;
2020-09-14 10:44:43 +01:00
logger . debug ( ` Languages API response: ${ JSON . stringify ( response ) } ` ) ;
2020-08-27 10:46:26 +01:00
// The GitHub API is going to return languages in order of popularity,
// When we pick a language to autobuild we want to pick the most popular traced language
// Since sets in javascript maintain insertion order, using a set here and then splatting it
// into an array gives us an array of languages ordered by popularity
2020-09-14 10:44:43 +01:00
const languages = new Set ( ) ;
for ( const lang of Object . keys ( response . data ) ) {
2021-09-10 13:53:13 -07:00
const parsedLang = ( 0 , languages _1 . parseLanguage ) ( lang ) ;
2020-08-27 10:46:26 +01:00
if ( parsedLang !== undefined ) {
languages . add ( parsedLang ) ;
2020-06-26 15:33:59 +01:00
}
}
2020-08-27 10:46:26 +01:00
return [ ... languages ] ;
2020-06-26 15:33:59 +01:00
}
2022-11-24 11:06:29 -08:00
exports . getLanguagesInRepo = getLanguagesInRepo ;
2020-06-26 15:33:59 +01:00
/**
* Get the languages to analyse.
*
* The result is obtained from the action input parameter 'languages' if that
* has been set, otherwise it is deduced as all languages in the repo that
* can be analysed.
2020-08-04 10:29:50 +01:00
*
* If no languages could be detected from either the workflow or the repository
* then throw an error.
2020-06-26 15:33:59 +01:00
*/
2022-11-14 19:48:27 +00:00
async function getLanguages ( codeQL , languagesInput , repository , logger ) {
2022-11-23 14:53:40 -08:00
// Obtain languages without filtering them.
const { rawLanguages , autodetected } = await getRawLanguages ( languagesInput , repository , logger ) ;
2023-09-14 15:26:38 +01:00
let languages = rawLanguages ;
2022-11-23 14:53:40 -08:00
if ( autodetected ) {
2023-09-14 15:26:38 +01:00
const supportedLanguages = Object . keys ( await codeQL . resolveLanguages ( ) ) ;
languages = languages
. map ( languages _1 . parseLanguage )
. filter ( ( value ) => value && supportedLanguages . includes ( value ) )
. map ( ( value ) => value ) ;
2022-11-23 14:53:40 -08:00
logger . info ( ` Automatically detected languages: ${ languages . join ( ", " ) } ` ) ;
}
else {
2023-09-14 15:26:38 +01:00
const aliases = await getLanguageAliases ( codeQL ) ;
if ( aliases ) {
languages = languages . map ( ( lang ) => aliases [ lang ] || lang ) ;
}
2022-11-23 14:53:40 -08:00
logger . info ( ` Languages from configuration: ${ languages . join ( ", " ) } ` ) ;
2020-06-26 15:33:59 +01:00
}
2020-08-04 10:29:50 +01:00
// If the languages parameter was not given and no languages were
// detected then fail here as this is a workflow configuration error.
if ( languages . length === 0 ) {
2024-02-08 09:20:03 -08:00
throw new util _1 . ConfigurationError ( getNoLanguagesError ( ) ) ;
2020-08-04 10:29:50 +01:00
}
2020-08-06 17:34:45 +01:00
// Make sure they are supported
2020-08-10 11:54:02 +01:00
const parsedLanguages = [ ] ;
2020-08-06 17:34:45 +01:00
const unknownLanguages = [ ] ;
2020-09-14 10:44:43 +01:00
for ( const language of languages ) {
2021-09-10 13:53:13 -07:00
const parsedLanguage = ( 0 , languages _1 . parseLanguage ) ( language ) ;
2020-08-10 11:54:02 +01:00
if ( parsedLanguage === undefined ) {
2020-08-06 17:34:45 +01:00
unknownLanguages . push ( language ) ;
}
2022-11-23 16:16:02 -08:00
else if ( ! parsedLanguages . includes ( parsedLanguage ) ) {
parsedLanguages . push ( parsedLanguage ) ;
2020-08-06 17:34:45 +01:00
}
}
2022-11-24 11:06:29 -08:00
// Any unknown languages here would have come directly from the input
// since we filter unknown languages coming from the GitHub API.
2020-08-06 17:34:45 +01:00
if ( unknownLanguages . length > 0 ) {
2024-02-08 09:20:03 -08:00
throw new util _1 . ConfigurationError ( getUnknownLanguagesError ( unknownLanguages ) ) ;
2020-08-04 10:29:50 +01:00
}
2020-08-10 11:54:02 +01:00
return parsedLanguages ;
2020-06-26 15:33:59 +01:00
}
2022-11-23 16:16:02 -08:00
exports . getLanguages = getLanguages ;
2023-09-14 15:26:38 +01:00
/**
* Gets the set of languages supported by CodeQL, along with their aliases if supported by the
* version of the CLI.
*/
async function getLanguageAliases ( codeql ) {
if ( await ( 0 , util _1 . codeQlVersionAbove ) ( codeql , codeql _1 . CODEQL _VERSION _LANGUAGE _ALIASING ) ) {
return ( await codeql . betterResolveLanguages ( ) ) . aliases ;
}
return undefined ;
}
exports . getLanguageAliases = getLanguageAliases ;
2022-11-23 14:53:40 -08:00
/**
* Gets the set of languages in the current repository without checking to
* see if these languages are actually supported by CodeQL.
*
* @param languagesInput The languages from the workflow input
* @param repository the owner/name of the repository
* @param logger a logger
* @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 ) {
// Obtain from action input 'languages' if set
let rawLanguages = ( languagesInput || "" )
. split ( "," )
. map ( ( x ) => x . trim ( ) . toLowerCase ( ) )
. filter ( ( x ) => x . length > 0 ) ;
let autodetected ;
if ( rawLanguages . length ) {
autodetected = false ;
}
else {
autodetected = true ;
2022-11-24 12:09:12 -08:00
// Obtain all languages in the repo that can be analysed
2022-11-23 14:53:40 -08:00
rawLanguages = ( await getLanguagesInRepo ( repository , logger ) ) ;
}
return { rawLanguages , autodetected } ;
}
exports . getRawLanguages = getRawLanguages ;
2020-08-24 15:37:07 +01:00
/**
* Get the default config for when the user has not supplied one.
*/
2024-02-12 22:47:05 +00:00
async function getDefaultConfig ( { languagesInput , queriesInput , packsInput , buildModeInput , dbLocation , trapCachingEnabled , debugMode , debugArtifactName , debugDatabaseName , repository , tempDir , codeql , githubVersion , features , logger , } ) {
2024-01-30 18:41:12 +00:00
const languages = await getLanguages ( codeql , languagesInput , repository , logger ) ;
2024-02-12 22:47:05 +00:00
const buildMode = await parseBuildModeInput ( buildModeInput , languages , features , logger ) ;
2024-01-30 18:41:12 +00:00
const augmentationProperties = calculateAugmentation ( packsInput , queriesInput , languages ) ;
const { trapCaches , trapCacheDownloadTime } = await downloadCacheWithTime ( trapCachingEnabled , codeql , languages , logger ) ;
2020-06-26 15:33:59 +01:00
return {
2020-09-14 10:44:43 +01:00
languages ,
2024-02-12 22:47:05 +00:00
buildMode ,
2020-07-20 16:33:37 +01:00
originalUserInput : { } ,
2020-08-19 15:11:49 +01:00
tempDir ,
2024-01-30 18:41:12 +00:00
codeQLCmd : codeql . getPath ( ) ,
gitHubVersion : githubVersion ,
2021-05-17 10:35:09 +01:00
dbLocation : dbLocationOrDefault ( dbLocation , tempDir ) ,
2021-10-28 14:15:22 +01:00
debugMode ,
2022-01-07 13:11:51 +00:00
debugArtifactName ,
debugDatabaseName ,
2022-06-19 16:44:24 -07:00
augmentationProperties ,
2022-08-15 14:44:43 +01:00
trapCaches ,
trapCacheDownloadTime ,
2020-06-26 15:33:59 +01:00
} ;
}
2020-07-15 17:36:49 +01:00
exports . getDefaultConfig = getDefaultConfig ;
2022-08-15 14:44:43 +01:00
async function downloadCacheWithTime ( trapCachingEnabled , codeQL , languages , logger ) {
let trapCaches = { } ;
let trapCacheDownloadTime = 0 ;
if ( trapCachingEnabled ) {
2022-08-19 11:12:10 +01:00
const start = perf _hooks _1 . performance . now ( ) ;
2022-08-15 14:44:43 +01:00
trapCaches = await ( 0 , trap _caching _1 . downloadTrapCaches ) ( codeQL , languages , logger ) ;
2022-08-19 11:12:10 +01:00
trapCacheDownloadTime = perf _hooks _1 . performance . now ( ) - start ;
2022-08-15 14:44:43 +01:00
}
return { trapCaches , trapCacheDownloadTime } ;
}
2020-06-26 15:33:59 +01:00
/**
* Load the config from the given file.
*/
2024-02-12 22:47:05 +00:00
async function loadConfig ( { languagesInput , queriesInput , packsInput , buildModeInput , configFile , dbLocation , trapCachingEnabled , debugMode , debugArtifactName , debugDatabaseName , repository , tempDir , codeql , workspacePath , githubVersion , apiDetails , features , logger , } ) {
2020-06-25 13:21:46 +01:00
let parsedYAML ;
if ( isLocal ( configFile ) ) {
2024-02-15 11:56:11 +00:00
if ( configFile !== userConfigFromActionPath ( tempDir ) ) {
// If the config file is not generated by the Action, it should be relative to the workspace.
configFile = path . resolve ( workspacePath , configFile ) ;
// Error if the config file is now outside of the workspace
if ( ! ( configFile + path . sep ) . startsWith ( workspacePath + path . sep ) ) {
throw new util _1 . ConfigurationError ( getConfigFileOutsideWorkspaceErrorMessage ( configFile ) ) ;
}
}
parsedYAML = getLocalConfig ( configFile ) ;
2020-05-05 17:32:58 +01:00
}
2020-06-25 13:21:46 +01:00
else {
2020-11-26 17:54:34 +00:00
parsedYAML = await getRemoteConfig ( configFile , apiDetails ) ;
2020-05-05 17:32:58 +01:00
}
2024-01-30 18:41:12 +00:00
const languages = await getLanguages ( codeql , languagesInput , repository , logger ) ;
2024-02-12 22:47:05 +00:00
const buildMode = await parseBuildModeInput ( buildModeInput , languages , features , logger ) ;
2024-01-30 18:41:12 +00:00
const augmentationProperties = calculateAugmentation ( packsInput , queriesInput , languages ) ;
const { trapCaches , trapCacheDownloadTime } = await downloadCacheWithTime ( trapCachingEnabled , codeql , languages , logger ) ;
2020-07-20 16:33:37 +01:00
return {
languages ,
2024-02-12 22:47:05 +00:00
buildMode ,
2020-08-19 15:11:49 +01:00
originalUserInput : parsedYAML ,
tempDir ,
2024-01-30 18:41:12 +00:00
codeQLCmd : codeql . getPath ( ) ,
gitHubVersion : githubVersion ,
2021-05-17 10:35:09 +01:00
dbLocation : dbLocationOrDefault ( dbLocation , tempDir ) ,
2021-10-28 14:15:22 +01:00
debugMode ,
2022-01-07 13:11:51 +00:00
debugArtifactName ,
debugDatabaseName ,
2022-06-19 16:44:24 -07:00
augmentationProperties ,
2022-08-15 14:44:43 +01:00
trapCaches ,
trapCacheDownloadTime ,
2022-06-19 16:44:24 -07:00
} ;
}
/**
* Calculates how the codeql config file needs to be augmented before passing
* it to the CLI. The reason this is necessary is the codeql-action can be called
* with extra inputs from the workflow. These inputs are not part of the config
* and the CLI does not know about these inputs so we need to inject them into
* the config file sent to the CLI.
*
* @param rawPacksInput The packs input from the action configuration.
* @param rawQueriesInput The queries input from the action configuration.
* @param languages The languages that the config file is for. If the packs input
* is non-empty, then there must be exactly one language. Otherwise, an
* error is thrown.
*
* @returns The properties that need to be augmented in the config file.
*
* @throws An error if the packs input is non-empty and the languages input does
* not have exactly one language.
*/
// exported for testing.
function calculateAugmentation ( rawPacksInput , rawQueriesInput , languages ) {
const packsInputCombines = shouldCombine ( rawPacksInput ) ;
const packsInput = parsePacksFromInput ( rawPacksInput , languages , packsInputCombines ) ;
const queriesInputCombines = shouldCombine ( rawQueriesInput ) ;
const queriesInput = parseQueriesFromInput ( rawQueriesInput , queriesInputCombines ) ;
return {
packsInputCombines ,
2023-01-18 20:00:33 +00:00
packsInput : packsInput ? . [ languages [ 0 ] ] ,
2022-06-19 16:44:24 -07:00
queriesInput ,
queriesInputCombines ,
2020-07-20 16:33:37 +01:00
} ;
2020-06-26 15:33:59 +01:00
}
2022-06-19 16:44:24 -07:00
exports . calculateAugmentation = calculateAugmentation ;
function parseQueriesFromInput ( rawQueriesInput , queriesInputCombines ) {
if ( ! rawQueriesInput ) {
return undefined ;
}
const trimmedInput = queriesInputCombines
? rawQueriesInput . trim ( ) . slice ( 1 ) . trim ( )
2023-02-07 10:40:49 -08:00
: rawQueriesInput ? . trim ( ) ? ? "" ;
2022-06-19 16:44:24 -07:00
if ( queriesInputCombines && trimmedInput . length === 0 ) {
2024-02-08 09:20:03 -08:00
throw new util _1 . ConfigurationError ( getConfigFilePropertyError ( undefined , "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." ) ) ;
2022-06-19 16:44:24 -07:00
}
return trimmedInput . split ( "," ) . map ( ( query ) => ( { uses : query . trim ( ) } ) ) ;
}
2021-06-04 10:18:24 -07:00
/**
* Pack names must be in the form of `scope/name`, with only alpha-numeric characters,
* and `-` allowed as long as not the first or last char.
**/
2021-06-03 09:32:44 -07:00
const PACK _IDENTIFIER _PATTERN = ( function ( ) {
const alphaNumeric = "[a-z0-9]" ;
const alphaNumericDash = "[a-z0-9-]" ;
const component = ` ${ alphaNumeric } ( ${ alphaNumericDash } * ${ alphaNumeric } )? ` ;
return new RegExp ( ` ^ ${ component } / ${ component } $ ` ) ;
} ) ( ) ;
// Exported for testing
2022-06-19 16:44:24 -07:00
function parsePacksFromInput ( rawPacksInput , languages , packsInputCombines ) {
2023-01-18 20:00:33 +00:00
if ( ! rawPacksInput ? . trim ( ) ) {
2021-06-23 15:41:52 -07:00
return undefined ;
}
if ( languages . length > 1 ) {
2024-02-08 09:20:03 -08:00
throw new util _1 . ConfigurationError ( "Cannot specify a 'packs' input in a multi-language analysis. Use a codeql-config.yml file instead and specify packs by language." ) ;
2021-06-23 15:41:52 -07:00
}
else if ( languages . length === 0 ) {
2024-02-08 09:20:03 -08:00
throw new util _1 . ConfigurationError ( "No languages specified. Cannot process the packs input." ) ;
2021-06-23 15:41:52 -07:00
}
2022-06-19 16:44:24 -07:00
rawPacksInput = rawPacksInput . trim ( ) ;
if ( packsInputCombines ) {
rawPacksInput = rawPacksInput . trim ( ) . substring ( 1 ) . trim ( ) ;
if ( ! rawPacksInput ) {
2024-02-08 09:20:03 -08:00
throw new util _1 . ConfigurationError ( getConfigFilePropertyError ( undefined , "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." ) ) ;
2021-06-23 15:41:52 -07:00
}
}
return {
2022-06-19 16:44:24 -07:00
[ languages [ 0 ] ] : rawPacksInput . split ( "," ) . reduce ( ( packs , pack ) => {
2024-01-04 16:27:05 +00:00
packs . push ( validatePackSpecification ( pack ) ) ;
2021-06-23 15:41:52 -07:00
return packs ;
} , [ ] ) ,
} ;
}
2024-01-04 16:27:05 +00:00
exports . parsePacksFromInput = parsePacksFromInput ;
2022-04-26 19:47:59 -07:00
/**
* Validates that this package specification is syntactically correct.
* It may not point to any real package, but after this function returns
* without throwing, we are guaranteed that the package specification
* is roughly correct.
*
* The CLI itself will do a more thorough validation of the package
* specification.
*
* A package specification looks like this:
*
* `scope/name@version:path`
*
* Version and path are optional.
*
* @param packStr the package specification to verify.
* @param configFile Config file to use for error reporting
*/
2024-01-04 16:27:05 +00:00
function parsePacksSpecification ( packStr ) {
2021-06-03 09:32:44 -07:00
if ( typeof packStr !== "string" ) {
2024-02-08 09:20:03 -08:00
throw new util _1 . ConfigurationError ( getPacksStrInvalid ( packStr ) ) ;
2021-06-03 09:32:44 -07:00
}
2022-04-26 19:47:59 -07:00
packStr = packStr . trim ( ) ;
const atIndex = packStr . indexOf ( "@" ) ;
const colonIndex = packStr . indexOf ( ":" , atIndex ) ;
const packStart = 0 ;
const versionStart = atIndex + 1 || undefined ;
const pathStart = colonIndex + 1 || undefined ;
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 )
: undefined ;
const pathEnd = pathStart ? packStr . length : undefined ;
const packName = packStr . slice ( packStart , packEnd ) . trim ( ) ;
const version = versionStart
? packStr . slice ( versionStart , versionEnd ) . trim ( )
: undefined ;
const packPath = pathStart
? packStr . slice ( pathStart , pathEnd ) . trim ( )
: undefined ;
if ( ! PACK _IDENTIFIER _PATTERN . test ( packName ) ) {
2024-02-08 09:20:03 -08:00
throw new util _1 . ConfigurationError ( getPacksStrInvalid ( packStr ) ) ;
2021-06-03 09:32:44 -07:00
}
2022-04-26 19:47:59 -07:00
if ( version ) {
try {
new semver . Range ( version ) ;
}
catch ( e ) {
// The range string is invalid. OK to ignore the caught error
2024-02-08 09:20:03 -08:00
throw new util _1 . ConfigurationError ( getPacksStrInvalid ( packStr ) ) ;
2021-06-03 09:32:44 -07:00
}
}
2022-04-26 19:47:59 -07:00
if ( packPath &&
2022-06-16 16:21:19 -07:00
( path . 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.
path . normalize ( packPath ) . split ( path . sep ) . join ( "/" ) !==
packPath . split ( path . sep ) . join ( "/" ) ) ) {
2024-02-08 09:20:03 -08:00
throw new util _1 . ConfigurationError ( getPacksStrInvalid ( packStr ) ) ;
2022-04-26 19:47:59 -07:00
}
if ( ! packPath && pathStart ) {
// 0 length path
2024-02-08 09:20:03 -08:00
throw new util _1 . ConfigurationError ( getPacksStrInvalid ( packStr ) ) ;
2022-04-26 19:47:59 -07:00
}
2022-06-27 13:13:55 -07:00
return {
name : packName ,
version ,
path : packPath ,
} ;
}
exports . parsePacksSpecification = parsePacksSpecification ;
2024-01-04 16:27:05 +00:00
function validatePackSpecification ( pack ) {
return ( 0 , util _1 . prettyPrintPack ) ( parsePacksSpecification ( pack ) ) ;
2021-06-03 09:32:44 -07:00
}
2022-06-27 13:13:55 -07:00
exports . validatePackSpecification = validatePackSpecification ;
2022-06-19 16:44:24 -07:00
/**
* The convention in this action is that an input value that is prefixed with a '+' will
* be combined with the corresponding value in the config file.
*
* Without a '+', an input value will override the corresponding value in the config file.
*
* @param inputValue The input value to process.
2023-02-07 06:09:33 -08:00
* @returns true if the input value should replace the corresponding value in the config file,
* false if it should be appended.
2022-06-19 16:44:24 -07:00
*/
function shouldCombine ( inputValue ) {
2023-01-18 20:00:33 +00:00
return ! ! inputValue ? . trim ( ) . startsWith ( "+" ) ;
2021-06-23 15:41:52 -07:00
}
2021-05-17 10:35:09 +01:00
function dbLocationOrDefault ( dbLocation , tempDir ) {
return dbLocation || path . resolve ( tempDir , "codeql_databases" ) ;
}
2024-02-15 11:56:11 +00:00
function userConfigFromActionPath ( tempDir ) {
return path . resolve ( tempDir , "user-config-from-action.yml" ) ;
}
2020-06-26 15:33:59 +01:00
/**
* Load and return the config.
*
* This will parse the config from the user input if present, or generate
* a default config. The parsed config is then stored to a known location.
*/
2024-01-30 18:41:12 +00:00
async function initConfig ( inputs ) {
2020-06-26 15:33:59 +01:00
let config ;
2024-02-13 15:59:45 +00:00
const { logger , tempDir } = inputs ;
2023-04-10 07:56:09 +02:00
// if configInput is set, it takes precedence over configFile
2024-01-30 18:41:12 +00:00
if ( inputs . configInput ) {
if ( inputs . configFile ) {
2023-04-18 05:43:21 +02:00
logger . warning ( ` Both a config file and config input were provided. Ignoring config file. ` ) ;
}
2024-02-15 11:56:11 +00:00
inputs . configFile = userConfigFromActionPath ( tempDir ) ;
2024-01-30 18:41:12 +00:00
fs . writeFileSync ( inputs . configFile , inputs . configInput ) ;
logger . debug ( ` Using config from action input: ${ inputs . configFile } ` ) ;
2023-03-18 06:37:28 +00:00
}
2020-06-26 15:33:59 +01:00
// If no config file was provided create an empty one
2024-01-30 18:41:12 +00:00
if ( ! inputs . configFile ) {
2020-09-14 10:44:43 +01:00
logger . debug ( "No configuration file was provided" ) ;
2024-01-30 18:41:12 +00:00
config = await getDefaultConfig ( inputs ) ;
2020-06-26 15:33:59 +01:00
}
else {
2024-01-30 18:41:12 +00:00
// Convince the type checker that inputs.configFile is defined.
config = await loadConfig ( { ... inputs , configFile : inputs . configFile } ) ;
2020-06-26 15:33:59 +01:00
}
// Save the config so we can easily access it again in the future
2020-08-25 16:19:15 +01:00
await saveConfig ( config , logger ) ;
2020-04-28 16:46:47 +02:00
return config ;
}
2020-06-26 15:33:59 +01:00
exports . initConfig = initConfig ;
2022-09-01 16:07:26 -07:00
function parseRegistries ( registriesInput ) {
try {
2022-09-06 10:41:32 -07:00
return registriesInput
? yaml . load ( registriesInput )
: undefined ;
2022-09-01 16:07:26 -07:00
}
catch ( e ) {
2024-02-08 09:20:03 -08:00
throw new util _1 . ConfigurationError ( "Invalid registries input. Must be a YAML string." ) ;
2022-09-01 16:07:26 -07:00
}
}
2020-06-24 16:15:25 +01:00
function isLocal ( configPath ) {
// If the path starts with ./, look locally
if ( configPath . indexOf ( "./" ) === 0 ) {
return true ;
}
2020-09-14 10:44:43 +01:00
return configPath . indexOf ( "@" ) === - 1 ;
2020-06-24 16:15:25 +01:00
}
2024-02-15 11:56:11 +00:00
function getLocalConfig ( configFile ) {
2020-06-25 13:21:46 +01:00
// Error if the file does not exist
if ( ! fs . existsSync ( configFile ) ) {
2024-02-08 09:20:03 -08:00
throw new util _1 . ConfigurationError ( getConfigFileDoesNotExistErrorMessage ( configFile ) ) ;
2020-06-25 13:21:46 +01:00
}
2021-07-27 22:12:26 +01:00
return yaml . load ( fs . readFileSync ( configFile , "utf8" ) ) ;
2020-06-25 13:21:46 +01:00
}
2020-11-26 17:54:34 +00:00
async function getRemoteConfig ( configFile , apiDetails ) {
2020-06-26 09:32:44 +01:00
// retrieve the various parts of the config location, and ensure they're present
2020-09-14 10:44:43 +01:00
const format = new RegExp ( "(?<owner>[^/]+)/(?<repo>[^/]+)/(?<path>[^@]+)@(?<ref>.*)" ) ;
2020-06-25 13:21:46 +01:00
const pieces = format . exec ( configFile ) ;
2020-06-26 09:32:44 +01:00
// 5 = 4 groups + the whole expression
if ( pieces === null || pieces . groups === undefined || pieces . length < 5 ) {
2024-02-08 09:20:03 -08:00
throw new util _1 . ConfigurationError ( getConfigFileRepoFormatInvalidMessage ( configFile ) ) ;
2020-06-25 13:21:46 +01:00
}
2021-04-09 14:17:46 -07:00
const response = await api
2022-11-14 19:48:27 +00:00
. getApiClientWithExternalAuth ( apiDetails )
2023-07-13 11:17:33 +01:00
. rest . repos . getContent ( {
2020-06-26 09:32:44 +01:00
owner : pieces . groups . owner ,
repo : pieces . groups . repo ,
path : pieces . groups . path ,
ref : pieces . groups . ref ,
} ) ;
let fileContents ;
2020-06-26 15:56:33 +01:00
if ( "content" in response . data && response . data . content !== undefined ) {
2020-06-26 09:32:44 +01:00
fileContents = response . data . content ;
2020-06-26 15:56:33 +01:00
}
else if ( Array . isArray ( response . data ) ) {
2024-02-08 09:20:03 -08:00
throw new util _1 . ConfigurationError ( getConfigFileDirectoryGivenMessage ( configFile ) ) ;
2020-06-26 15:56:33 +01:00
}
else {
2024-02-08 09:20:03 -08:00
throw new util _1 . ConfigurationError ( getConfigFileFormatInvalidMessage ( configFile ) ) ;
2020-06-26 15:56:33 +01:00
}
2021-07-27 22:12:26 +01:00
return yaml . load ( Buffer . from ( fileContents , "base64" ) . toString ( "binary" ) ) ;
2020-06-25 13:21:46 +01:00
}
2020-06-26 15:33:59 +01:00
/**
* Get the file path where the parsed config will be stored.
*/
2020-08-19 15:11:49 +01:00
function getPathToParsedConfigFile ( tempDir ) {
2020-09-14 10:44:43 +01:00
return path . join ( tempDir , "config" ) ;
2020-05-05 17:32:58 +01:00
}
2020-07-15 17:36:49 +01:00
exports . getPathToParsedConfigFile = getPathToParsedConfigFile ;
2020-06-26 15:33:59 +01:00
/**
2020-07-15 17:36:49 +01:00
* Store the given config to the path returned from getPathToParsedConfigFile.
2020-06-26 15:33:59 +01:00
*/
2020-08-25 16:19:15 +01:00
async function saveConfig ( config , logger ) {
2020-04-28 16:46:47 +02:00
const configString = JSON . stringify ( config ) ;
2020-08-19 15:11:49 +01:00
const configFile = getPathToParsedConfigFile ( config . tempDir ) ;
2020-08-07 17:46:46 +01:00
fs . mkdirSync ( path . dirname ( configFile ) , { recursive : true } ) ;
2020-09-14 10:44:43 +01:00
fs . writeFileSync ( configFile , configString , "utf8" ) ;
logger . debug ( "Saved config:" ) ;
2020-08-25 16:19:15 +01:00
logger . debug ( configString ) ;
2020-04-28 16:46:47 +02:00
}
2020-06-26 15:33:59 +01:00
/**
2020-08-28 09:43:25 +01:00
* Get the config that has been saved to the given temp dir.
* If the config could not be found then returns undefined.
2020-06-26 15:33:59 +01:00
*/
2020-08-25 16:19:15 +01:00
async function getConfig ( tempDir , logger ) {
2020-08-19 15:11:49 +01:00
const configFile = getPathToParsedConfigFile ( tempDir ) ;
2020-06-26 15:33:59 +01:00
if ( ! fs . existsSync ( configFile ) ) {
2020-08-28 09:43:25 +01:00
return undefined ;
2020-04-28 16:46:47 +02:00
}
2020-09-14 10:44:43 +01:00
const configString = fs . readFileSync ( configFile , "utf8" ) ;
logger . debug ( "Loaded config:" ) ;
2020-08-25 16:19:15 +01:00
logger . debug ( configString ) ;
2020-06-26 15:33:59 +01:00
return JSON . parse ( configString ) ;
2020-04-28 16:46:47 +02:00
}
2020-06-26 15:33:59 +01:00
exports . getConfig = getConfig ;
2023-02-07 10:40:49 -08:00
/**
* Generate a `qlconfig.yml` file from the `registries` input.
* This file is used by the CodeQL CLI to list the registries to use for each
* pack.
*
* @param registriesInput The value of the `registries` input.
* @param codeQL a codeQL object, used only for checking the version of CodeQL.
2023-02-09 11:19:27 -08:00
* @param tempDir a temporary directory to store the generated qlconfig.yml file.
2023-02-07 10:40:49 -08:00
* @param logger a logger object.
* @returns The path to the generated `qlconfig.yml` file and the auth tokens to
* use for each registry.
*/
2023-09-27 16:16:13 +01:00
async function generateRegistries ( registriesInput , tempDir , logger ) {
2023-02-07 10:40:49 -08:00
const registries = parseRegistries ( registriesInput ) ;
let registriesAuthTokens ;
let qlconfigFile ;
if ( registries ) {
// generate a qlconfig.yml file to hold the registry configs.
const qlconfig = createRegistriesBlock ( registries ) ;
2023-02-09 11:19:27 -08:00
qlconfigFile = path . join ( tempDir , "qlconfig.yml" ) ;
2023-02-07 10:40:49 -08:00
const qlconfigContents = yaml . dump ( qlconfig ) ;
fs . writeFileSync ( qlconfigFile , qlconfigContents , "utf8" ) ;
logger . debug ( "Generated qlconfig.yml:" ) ;
logger . debug ( qlconfigContents ) ;
registriesAuthTokens = registries
. map ( ( registry ) => ` ${ registry . url } = ${ registry . token } ` )
. join ( "," ) ;
}
2023-02-27 09:59:16 -08:00
if ( typeof process . env . CODEQL _REGISTRIES _AUTH === "string" ) {
logger . debug ( "Using CODEQL_REGISTRIES_AUTH environment variable to authenticate with registries." ) ;
}
2023-02-09 11:19:27 -08:00
return {
registriesAuthTokens :
// if the user has explicitly set the CODEQL_REGISTRIES_AUTH env var then use that
2023-02-09 12:58:15 -08:00
process . env . CODEQL _REGISTRIES _AUTH ? ? registriesAuthTokens ,
2023-02-09 11:19:27 -08:00
qlconfigFile ,
} ;
2023-02-07 10:40:49 -08:00
}
exports . generateRegistries = generateRegistries ;
2022-09-01 16:07:26 -07:00
function createRegistriesBlock ( registries ) {
2022-09-07 20:32:47 -07:00
if ( ! Array . isArray ( registries ) ||
registries . some ( ( r ) => ! r . url || ! r . packages ) ) {
2024-02-08 09:20:03 -08:00
throw new util _1 . ConfigurationError ( "Invalid 'registries' input. Must be an array of objects with 'url' and 'packages' properties." ) ;
2022-09-07 20:32:47 -07:00
}
2022-09-01 16:07:26 -07:00
// be sure to remove the `token` field from the registry before writing it to disk.
const safeRegistries = registries . map ( ( registry ) => ( {
2022-09-07 20:32:47 -07:00
// ensure the url ends with a slash to avoid a bug in the CLI 2.10.4
2023-01-18 20:00:33 +00:00
url : ! registry ? . url . endsWith ( "/" ) ? ` ${ registry . url } / ` : registry . url ,
2022-09-01 16:07:26 -07:00
packages : registry . packages ,
} ) ) ;
const qlconfig = {
registries : safeRegistries ,
} ;
return qlconfig ;
}
2022-09-06 10:41:32 -07:00
/**
* Create a temporary environment based on the existing environment and overridden
* by the given environment variables that are passed in as arguments.
*
* Use this new environment in the context of the given operation. After completing
* the operation, restore the original environment.
*
* This function does not support un-setting environment variables.
*
* @param env
* @param operation
*/
2022-08-29 12:57:46 -07:00
async function wrapEnvironment ( env , operation ) {
// Remember the original env
const oldEnv = { ... process . env } ;
// Set the new env
for ( const [ key , value ] of Object . entries ( env ) ) {
// Ignore undefined keys
if ( value !== undefined ) {
process . env [ key ] = value ;
2022-08-26 13:48:08 -07:00
}
}
2022-08-29 12:57:46 -07:00
try {
// Run the operation
await operation ( ) ;
2022-08-30 08:54:21 -07:00
}
2022-08-29 12:57:46 -07:00
finally {
// Restore the old env
for ( const [ key , value ] of Object . entries ( oldEnv ) ) {
process . env [ key ] = value ;
}
2022-08-30 08:54:21 -07:00
}
2022-08-26 13:48:08 -07:00
}
2023-02-07 10:40:49 -08:00
exports . wrapEnvironment = wrapEnvironment ;
2024-02-12 22:47:05 +00:00
// Exported for testing
async function parseBuildModeInput ( input , languages , features , logger ) {
if ( input === undefined ) {
2024-02-02 19:13:42 +00:00
return undefined ;
}
2024-02-12 22:47:05 +00:00
if ( ! Object . values ( BuildMode ) . includes ( input ) ) {
throw new util _1 . ConfigurationError ( ` Invalid build mode: ' ${ input } '. Supported build modes are: ${ Object . values ( BuildMode ) . join ( ", " ) } . ` ) ;
}
if ( languages . includes ( languages _1 . Language . java ) &&
( await features . getValue ( feature _flags _1 . Feature . DisableJavaBuildlessEnabled ) ) ) {
logger . warning ( "Scanning Java code without a build is temporarily unavailable. Falling back to 'autobuild' build mode." ) ;
return BuildMode . Autobuild ;
2024-02-02 19:13:42 +00:00
}
2024-02-12 22:47:05 +00:00
return input ;
2024-02-02 19:13:42 +00:00
}
2024-02-12 22:47:05 +00:00
exports . parseBuildModeInput = parseBuildModeInput ;
2020-05-13 16:31:24 +01:00
//# sourceMappingURL=config-utils.js.map