2020-04-28 16:46:47 +02:00
"use strict" ;
var _ _importStar = ( this && this . _ _importStar ) || function ( mod ) {
if ( mod && mod . _ _esModule ) return mod ;
var result = { } ;
if ( mod != null ) for ( var k in mod ) if ( Object . hasOwnProperty . call ( mod , k ) ) result [ k ] = mod [ k ] ;
result [ "default" ] = mod ;
return result ;
} ;
Object . defineProperty ( exports , "__esModule" , { value : true } ) ;
2020-09-29 14:43:37 +01:00
const fs = _ _importStar ( require ( "fs" ) ) ;
const path = _ _importStar ( require ( "path" ) ) ;
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" ) ) ;
const externalQueries = _ _importStar ( require ( "./external-queries" ) ) ;
2020-08-10 11:54:02 +01:00
const languages _1 = require ( "./languages" ) ;
2020-06-26 15:33:59 +01:00
// Property names from the user-supplied config file.
2020-09-14 10:44:43 +01:00
const NAME _PROPERTY = "name" ;
const DISABLE _DEFAULT _QUERIES _PROPERTY = "disable-default-queries" ;
const QUERIES _PROPERTY = "queries" ;
const QUERIES _USES _PROPERTY = "uses" ;
const PATHS _IGNORE _PROPERTY = "paths-ignore" ;
const PATHS _PROPERTY = "paths" ;
2021-06-03 09:32:44 -07:00
const PACKS _PROPERTY = "packs" ;
2020-06-26 15:33:59 +01:00
/**
* A list of queries from https://github.com/github/codeql that
* we don't want to run. Disabling them here is a quicker alternative to
* disabling them in the code scanning query suites. Queries should also
* be disabled in the suites, and removed from this list here once the
* bundle is updated to make those suite changes live.
*
* Format is a map from language to an array of path suffixes of .ql files.
*/
const DISABLED _BUILTIN _QUERIES = {
2020-09-14 10:44:43 +01:00
csharp : [
"ql/src/Security Features/CWE-937/VulnerablePackage.ql" ,
"ql/src/Security Features/CWE-451/MissingXFrameOptions.ql" ,
] ,
2020-06-26 15:33:59 +01:00
} ;
function queryIsDisabled ( language , query ) {
2020-09-14 10:44:43 +01:00
return ( DISABLED _BUILTIN _QUERIES [ language ] || [ ] ) . some ( ( disabledQuery ) => query . endsWith ( disabledQuery ) ) ;
2020-04-28 16:46:47 +02:00
}
2020-07-15 17:36:49 +01:00
/**
* Asserts that the noDeclaredLanguage and multipleDeclaredLanguages fields are
* both empty and errors if they are not.
*/
function validateQueries ( resolvedQueries ) {
const noDeclaredLanguage = resolvedQueries . noDeclaredLanguage ;
const noDeclaredLanguageQueries = Object . keys ( noDeclaredLanguage ) ;
if ( noDeclaredLanguageQueries . length !== 0 ) {
2020-09-14 10:44:43 +01:00
throw new Error ( ` ${ "The following queries do not declare a language. " +
"Their qlpack.yml files are either missing or is invalid.\n" } ${ noDeclaredLanguageQueries . join ( "\n" ) } ` ) ;
2020-07-15 17:36:49 +01:00
}
const multipleDeclaredLanguages = resolvedQueries . multipleDeclaredLanguages ;
const multipleDeclaredLanguagesQueries = Object . keys ( multipleDeclaredLanguages ) ;
if ( multipleDeclaredLanguagesQueries . length !== 0 ) {
2020-09-14 10:44:43 +01:00
throw new Error ( ` ${ "The following queries declare multiple languages. " +
"Their qlpack.yml files are either missing or is invalid.\n" } ${ multipleDeclaredLanguagesQueries . join ( "\n" ) } ` ) ;
2020-07-15 17:36:49 +01:00
}
}
2020-06-26 15:33:59 +01:00
/**
* Run 'codeql resolve queries' and add the results to resultMap
2020-09-10 18:02:33 +01:00
*
* If a checkout path is given then the queries are assumed to be custom queries
* and an error will be thrown if there is anything invalid about the queries.
* If a checkout path is not given then the queries are assumed to be builtin
* queries, and error checking will be suppressed.
2020-06-26 15:33:59 +01:00
*/
2020-09-10 18:02:33 +01:00
async function runResolveQueries ( codeQL , resultMap , toResolve , extraSearchPath ) {
2020-08-19 15:54:23 +01:00
const resolvedQueries = await codeQL . resolveQueries ( toResolve , extraSearchPath ) ;
2020-09-10 18:02:33 +01:00
if ( extraSearchPath !== undefined ) {
validateQueries ( resolvedQueries ) ;
}
for ( const [ language , queryPaths ] of Object . entries ( resolvedQueries . byLanguage ) ) {
2020-06-26 15:33:59 +01:00
if ( resultMap [ language ] === undefined ) {
2020-09-10 18:02:33 +01:00
resultMap [ language ] = {
builtin : [ ] ,
custom : [ ] ,
} ;
}
2020-09-18 09:52:44 +01:00
const queries = Object . keys ( queryPaths ) . filter ( ( q ) => ! queryIsDisabled ( language , q ) ) ;
2020-09-10 18:02:33 +01:00
if ( extraSearchPath !== undefined ) {
2021-04-01 12:38:13 +01:00
resultMap [ language ] . custom . push ( {
searchPath : extraSearchPath ,
queries ,
} ) ;
2020-09-10 18:02:33 +01:00
}
else {
resultMap [ language ] . builtin . push ( ... queries ) ;
2020-04-28 16:46:47 +02:00
}
}
}
2020-06-26 15:33:59 +01:00
/**
* Get the set of queries included by default.
*/
2020-08-19 15:54:23 +01:00
async function addDefaultQueries ( codeQL , languages , resultMap ) {
2020-09-14 10:44:43 +01:00
const suites = languages . map ( ( l ) => ` ${ l } -code-scanning.qls ` ) ;
2020-09-10 18:02:33 +01:00
await runResolveQueries ( codeQL , resultMap , suites , undefined ) ;
2020-06-26 15:33:59 +01:00
}
// The set of acceptable values for built-in suites from the codeql bundle
2020-09-14 10:44:43 +01:00
const builtinSuites = [ "security-extended" , "security-and-quality" ] ;
2020-06-26 15:33:59 +01:00
/**
2020-07-15 17:36:49 +01:00
* Determine the set of queries associated with suiteName's suites and add them to resultMap.
* Throws an error if suiteName is not a valid builtin suite.
2020-06-26 15:33:59 +01:00
*/
2020-08-25 10:39:53 +01:00
async function addBuiltinSuiteQueries ( languages , codeQL , resultMap , suiteName , configFile ) {
2020-11-19 23:03:45 +01:00
const found = builtinSuites . find ( ( suite ) => suite === suiteName ) ;
if ( ! found ) {
2020-06-26 15:33:59 +01:00
throw new Error ( getQueryUsesInvalid ( configFile , suiteName ) ) ;
}
2020-09-14 10:44:43 +01:00
const suites = languages . map ( ( l ) => ` ${ l } - ${ suiteName } .qls ` ) ;
2020-09-10 18:02:33 +01:00
await runResolveQueries ( codeQL , resultMap , suites , undefined ) ;
2020-06-26 15:33:59 +01:00
}
/**
2020-07-15 17:36:49 +01:00
* Retrieve the set of queries at localQueryPath and add them to resultMap.
2020-06-26 15:33:59 +01:00
*/
2021-07-01 11:38:14 +02:00
async function addLocalQueries ( codeQL , resultMap , localQueryPath , workspacePath , configFile ) {
2020-06-26 15:33:59 +01:00
// Resolve the local path against the workspace so that when this is
// passed to codeql it resolves to exactly the path we expect it to resolve to.
2021-07-01 11:38:14 +02:00
let absoluteQueryPath = path . join ( workspacePath , localQueryPath ) ;
2020-06-26 15:33:59 +01:00
// Check the file exists
if ( ! fs . existsSync ( absoluteQueryPath ) ) {
throw new Error ( getLocalPathDoesNotExist ( configFile , localQueryPath ) ) ;
}
// Call this after checking file exists, because it'll fail if file doesn't exist
absoluteQueryPath = fs . realpathSync ( absoluteQueryPath ) ;
// Check the local path doesn't jump outside the repo using '..' or symlinks
2021-07-01 11:38:14 +02:00
if ( ! ( absoluteQueryPath + path . sep ) . startsWith ( fs . realpathSync ( workspacePath ) + path . sep ) ) {
2020-06-26 15:33:59 +01:00
throw new Error ( getLocalPathOutsideOfRepository ( configFile , localQueryPath ) ) ;
}
2021-07-01 11:38:14 +02:00
const extraSearchPath = workspacePath ;
await runResolveQueries ( codeQL , resultMap , [ absoluteQueryPath ] , extraSearchPath ) ;
2020-06-26 15:33:59 +01:00
}
/**
2020-07-15 17:36:49 +01:00
* Retrieve the set of queries at the referenced remote repo and add them to resultMap.
2020-06-26 15:33:59 +01:00
*/
2021-01-04 12:31:55 +00:00
async function addRemoteQueries ( codeQL , resultMap , queryUses , tempDir , apiDetails , logger , configFile ) {
2020-09-14 10:44:43 +01:00
let tok = queryUses . split ( "@" ) ;
2020-06-26 15:33:59 +01:00
if ( tok . length !== 2 ) {
throw new Error ( getQueryUsesInvalid ( configFile , queryUses ) ) ;
}
const ref = tok [ 1 ] ;
2020-09-14 10:44:43 +01:00
tok = tok [ 0 ] . split ( "/" ) ;
2020-06-26 15:33:59 +01:00
// The first token is the owner
// The second token is the repo
// The rest is a path, if there is more than one token combine them to form the full path
if ( tok . length < 2 ) {
throw new Error ( getQueryUsesInvalid ( configFile , queryUses ) ) ;
}
// Check none of the parts of the repository name are empty
2020-09-14 10:44:43 +01:00
if ( tok [ 0 ] . trim ( ) === "" || tok [ 1 ] . trim ( ) === "" ) {
2020-06-26 15:33:59 +01:00
throw new Error ( getQueryUsesInvalid ( configFile , queryUses ) ) ;
}
2020-09-14 10:44:43 +01:00
const nwo = ` ${ tok [ 0 ] } / ${ tok [ 1 ] } ` ;
2020-06-26 15:33:59 +01:00
// Checkout the external repository
2021-01-04 12:31:55 +00:00
const checkoutPath = await externalQueries . checkoutExternalRepository ( nwo , ref , apiDetails , tempDir , logger ) ;
2020-06-26 15:33:59 +01:00
const queryPath = tok . length > 2
2020-09-14 10:44:43 +01:00
? path . join ( checkoutPath , tok . slice ( 2 ) . join ( "/" ) )
2020-08-25 16:19:15 +01:00
: checkoutPath ;
2020-09-10 18:02:33 +01:00
await runResolveQueries ( codeQL , resultMap , [ queryPath ] , checkoutPath ) ;
2020-06-26 15:33:59 +01:00
}
/**
* Parse a query 'uses' field to a discrete set of query files and update resultMap.
2020-07-15 17:36:49 +01:00
*
* The logic for parsing the string is based on what actions does for
* parsing the 'uses' actions in the workflow file. So it can handle
* local paths starting with './', or references to remote repos, or
* a finite set of hardcoded terms for builtin suites.
2020-06-26 15:33:59 +01:00
*/
2021-07-01 11:38:14 +02:00
async function parseQueryUses ( languages , codeQL , resultMap , queryUses , tempDir , workspacePath , apiDetails , logger , configFile ) {
2020-06-26 15:33:59 +01:00
queryUses = queryUses . trim ( ) ;
if ( queryUses === "" ) {
throw new Error ( getQueryUsesInvalid ( configFile ) ) ;
}
// Check for the local path case before we start trying to parse the repository name
if ( queryUses . startsWith ( "./" ) ) {
2021-07-01 11:38:14 +02:00
await addLocalQueries ( codeQL , resultMap , queryUses . slice ( 2 ) , workspacePath , configFile ) ;
2020-06-26 15:33:59 +01:00
return ;
}
// Check for one of the builtin suites
2020-09-14 10:44:43 +01:00
if ( queryUses . indexOf ( "/" ) === - 1 && queryUses . indexOf ( "@" ) === - 1 ) {
2020-08-25 10:39:53 +01:00
await addBuiltinSuiteQueries ( languages , codeQL , resultMap , queryUses , configFile ) ;
2020-06-26 15:33:59 +01:00
return ;
}
// Otherwise, must be a reference to another repo
2021-01-04 12:31:55 +00:00
await addRemoteQueries ( codeQL , resultMap , queryUses , tempDir , apiDetails , logger , configFile ) ;
2020-06-26 15:33:59 +01:00
}
2020-07-08 15:06:45 +01:00
// Regex validating stars in paths or paths-ignore entries.
// The intention is to only allow ** to appear when immediately
// preceded and followed by a slash.
const pathStarsRegex = /.*(?:\*\*[^/].*|\*\*$|[^/]\*\*.*)/ ;
2020-07-09 17:01:53 +01:00
// Characters that are supported by filters in workflows, but not by us.
// See https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions#filter-pattern-cheat-sheet
2020-09-28 10:55:58 +08:00
const filterPatternCharactersRegex = /.*[?+[\]!].*/ ;
2020-07-08 15:06:45 +01:00
// Checks that a paths of paths-ignore entry is valid, possibly modifying it
// to make it valid, or if not possible then throws an error.
2020-08-25 16:19:15 +01:00
function validateAndSanitisePath ( originalPath , propertyName , configFile , logger ) {
2020-07-08 16:22:29 +01:00
// Take a copy so we don't modify the original path, so we can still construct error messages
2020-11-19 23:03:45 +01:00
let newPath = originalPath ;
2020-07-08 16:22:29 +01:00
// All paths are relative to the src root, so strip off leading slashes.
2020-11-19 23:03:45 +01:00
while ( newPath . charAt ( 0 ) === "/" ) {
newPath = newPath . substring ( 1 ) ;
2020-07-08 16:22:29 +01:00
}
// Trailing ** are redundant, so strip them off
2020-11-19 23:03:45 +01:00
if ( newPath . endsWith ( "/**" ) ) {
newPath = newPath . substring ( 0 , newPath . length - 2 ) ;
2020-07-08 15:06:45 +01:00
}
2020-07-10 15:02:18 +01:00
// An empty path is not allowed as it's meaningless
2020-11-19 23:03:45 +01:00
if ( newPath === "" ) {
2020-09-14 10:44:43 +01:00
throw new Error ( getConfigFilePropertyError ( configFile , propertyName , ` " ${ originalPath } " is not an invalid path. ` +
` It is not necessary to include it, and it is not allowed to exclude it. ` ) ) ;
2020-07-10 15:02:18 +01:00
}
2020-07-09 17:01:53 +01:00
// Check for illegal uses of **
2020-11-19 23:03:45 +01:00
if ( newPath . match ( pathStarsRegex ) ) {
2020-09-14 10:44:43 +01:00
throw new Error ( getConfigFilePropertyError ( configFile , propertyName , ` " ${ originalPath } " contains an invalid "**" wildcard. ` +
2020-11-20 11:35:59 +01:00
` They must be immediately preceded and followed by a slash as in "/**/", or come at the start or end. ` ) ) ;
2020-07-08 15:06:45 +01:00
}
2020-07-09 17:01:53 +01:00
// Check for other regex characters that we don't support.
// Output a warning so the user knows, but otherwise continue normally.
2020-11-19 23:03:45 +01:00
if ( newPath . match ( filterPatternCharactersRegex ) ) {
2020-09-14 10:44:43 +01:00
logger . warning ( getConfigFilePropertyError ( configFile , propertyName , ` " ${ originalPath } " contains an unsupported character. ` +
` The filter pattern characters ?, +, [, ], ! are not supported and will be matched literally. ` ) ) ;
2020-07-09 17:01:53 +01:00
}
2020-07-09 18:27:46 +01:00
// Ban any uses of backslash for now.
// This may not play nicely with project layouts.
// This restriction can be lifted later if we determine they are ok.
2020-11-19 23:03:45 +01:00
if ( newPath . indexOf ( "\\" ) !== - 1 ) {
2020-09-14 10:44:43 +01:00
throw new Error ( getConfigFilePropertyError ( configFile , propertyName , ` " ${ originalPath } " contains an " \\ " character. These are not allowed in filters. ` +
` If running on windows we recommend using "/" instead for path filters. ` ) ) ;
2020-07-09 18:27:46 +01:00
}
2020-11-19 23:03:45 +01:00
return newPath ;
2020-07-08 15:06:45 +01:00
}
exports . validateAndSanitisePath = validateAndSanitisePath ;
2020-08-24 16:44:59 +01:00
// An undefined configFile in some of these functions indicates that
// the property was in a workflow file, not a config file
2020-05-26 19:23:28 +01:00
function getNameInvalid ( configFile ) {
2020-09-14 10:44:43 +01:00
return getConfigFilePropertyError ( configFile , NAME _PROPERTY , "must be a non-empty string" ) ;
2020-05-05 17:32:58 +01:00
}
2020-05-26 19:23:28 +01:00
exports . getNameInvalid = getNameInvalid ;
function getDisableDefaultQueriesInvalid ( configFile ) {
2020-09-14 10:44:43 +01:00
return getConfigFilePropertyError ( configFile , DISABLE _DEFAULT _QUERIES _PROPERTY , "must be a boolean" ) ;
2020-05-05 17:32:58 +01:00
}
2020-05-26 19:23:28 +01:00
exports . getDisableDefaultQueriesInvalid = getDisableDefaultQueriesInvalid ;
function getQueriesInvalid ( configFile ) {
2020-09-14 10:44:43 +01:00
return getConfigFilePropertyError ( configFile , QUERIES _PROPERTY , "must be an array" ) ;
2020-05-26 19:23:28 +01:00
}
exports . getQueriesInvalid = getQueriesInvalid ;
function getQueryUsesInvalid ( configFile , queryUses ) {
2020-09-14 10:44:43 +01:00
return getConfigFilePropertyError ( configFile , ` ${ QUERIES _PROPERTY } . ${ QUERIES _USES _PROPERTY } ` , ` must be a built-in suite ( ${ builtinSuites . join ( " or " ) } ), a relative path, or be of the form "owner/repo[/path]@ref" ${ queryUses !== undefined ? ` \n Found: ${ queryUses } ` : "" } ` ) ;
2020-05-26 19:23:28 +01:00
}
exports . getQueryUsesInvalid = getQueryUsesInvalid ;
function getPathsIgnoreInvalid ( configFile ) {
2020-09-14 10:44:43 +01:00
return getConfigFilePropertyError ( configFile , PATHS _IGNORE _PROPERTY , "must be an array of non-empty strings" ) ;
2020-05-26 19:23:28 +01:00
}
exports . getPathsIgnoreInvalid = getPathsIgnoreInvalid ;
function getPathsInvalid ( configFile ) {
2020-09-14 10:44:43 +01:00
return getConfigFilePropertyError ( configFile , PATHS _PROPERTY , "must be an array of non-empty strings" ) ;
2020-05-26 19:23:28 +01:00
}
exports . getPathsInvalid = getPathsInvalid ;
2021-06-03 09:32:44 -07:00
function getPacksRequireLanguage ( lang , configFile ) {
return getConfigFilePropertyError ( configFile , PACKS _PROPERTY , ` has " ${ lang } ", but it is not one of the languages to analyze ` ) ;
}
exports . getPacksRequireLanguage = getPacksRequireLanguage ;
function getPacksInvalidSplit ( configFile ) {
return getConfigFilePropertyError ( configFile , PACKS _PROPERTY , "must split packages by language" ) ;
}
exports . getPacksInvalidSplit = getPacksInvalidSplit ;
function getPacksInvalid ( configFile ) {
return getConfigFilePropertyError ( configFile , PACKS _PROPERTY , "must be an array of non-empty strings" ) ;
}
exports . getPacksInvalid = getPacksInvalid ;
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-06-08 13:40:17 +01:00
function getLocalPathOutsideOfRepository ( configFile , localPath ) {
2020-09-14 10:44:43 +01:00
return getConfigFilePropertyError ( configFile , ` ${ QUERIES _PROPERTY } . ${ QUERIES _USES _PROPERTY } ` , ` is invalid as the local path " ${ localPath } " is outside of the repository ` ) ;
2020-05-26 16:02:22 +01:00
}
exports . getLocalPathOutsideOfRepository = getLocalPathOutsideOfRepository ;
2020-06-08 13:40:17 +01:00
function getLocalPathDoesNotExist ( configFile , localPath ) {
2020-09-14 10:44:43 +01:00
return getConfigFilePropertyError ( configFile , ` ${ QUERIES _PROPERTY } . ${ QUERIES _USES _PROPERTY } ` , ` is invalid as the local path " ${ localPath } " does not exist in the repository ` ) ;
2020-05-26 16:02:22 +01:00
}
exports . getLocalPathDoesNotExist = getLocalPathDoesNotExist ;
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 ) {
2020-09-14 10:44:43 +01:00
return ` Did not recognise the following languages: ${ languages . join ( ", " ) } ` ;
2020-08-06 17:45:42 +01:00
}
exports . getUnknownLanguagesError = getUnknownLanguagesError ;
2020-06-26 15:33:59 +01:00
/**
* Gets the set of languages in the current repository
*/
2020-11-26 17:54:34 +00:00
async function getLanguagesInRepo ( repository , apiDetails , logger ) {
2020-08-27 10:46:26 +01:00
logger . debug ( ` GitHub repo ${ repository . owner } ${ repository . repo } ` ) ;
2021-06-01 15:04:15 -07:00
const response = await api . getApiClient ( apiDetails ) . 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 ) ) {
const parsedLang = 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
}
/**
* 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
*/
2021-05-23 16:27:46 +02:00
async function getLanguages ( codeQL , languagesInput , repository , apiDetails , logger ) {
2020-06-26 15:33:59 +01:00
// Obtain from action input 'languages' if set
2020-08-25 16:19:15 +01:00
let languages = ( languagesInput || "" )
2020-09-14 10:44:43 +01:00
. split ( "," )
. map ( ( x ) => x . trim ( ) )
. filter ( ( x ) => x . length > 0 ) ;
logger . info ( ` Languages from configuration: ${ JSON . stringify ( languages ) } ` ) ;
2020-06-26 15:33:59 +01:00
if ( languages . length === 0 ) {
// Obtain languages as all languages in the repo that can be analysed
2020-11-26 17:54:34 +00:00
languages = await getLanguagesInRepo ( repository , apiDetails , logger ) ;
2021-05-23 16:27:46 +02:00
const availableLanguages = await codeQL . resolveLanguages ( ) ;
languages = languages . filter ( ( value ) => value in availableLanguages ) ;
2020-09-14 10:44:43 +01:00
logger . info ( ` Automatically detected languages: ${ JSON . stringify ( languages ) } ` ) ;
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 ) {
2020-08-06 17:45:42 +01:00
throw new Error ( 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 ) {
2020-08-10 11:54:02 +01:00
const parsedLanguage = languages _1 . parseLanguage ( language ) ;
if ( parsedLanguage === undefined ) {
2020-08-06 17:34:45 +01:00
unknownLanguages . push ( language ) ;
}
2020-08-10 11:54:02 +01:00
else if ( parsedLanguages . indexOf ( parsedLanguage ) === - 1 ) {
parsedLanguages . push ( parsedLanguage ) ;
2020-08-06 17:34:45 +01:00
}
}
if ( unknownLanguages . length > 0 ) {
2020-08-06 17:45:42 +01:00
throw new Error ( 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
}
2021-07-01 11:38:14 +02:00
async function addQueriesFromWorkflow ( codeQL , queriesInput , languages , resultMap , tempDir , workspacePath , apiDetails , logger ) {
2020-09-08 10:00:16 +01:00
queriesInput = queriesInput . trim ( ) ;
// "+" means "don't override config file" - see shouldAddConfigFileQueries
2020-09-14 10:44:43 +01:00
queriesInput = queriesInput . replace ( /^\+/ , "" ) ;
for ( const query of queriesInput . split ( "," ) ) {
2021-07-01 11:38:14 +02:00
await parseQueryUses ( languages , codeQL , resultMap , query , tempDir , workspacePath , apiDetails , logger ) ;
2020-08-24 14:33:02 +01:00
}
2020-08-27 17:11:56 +01:00
}
// Returns true if either no queries were provided in the workflow.
// or if the queries in the workflow were provided in "additive" mode,
// indicating that they shouldn't override the config queries but
// should instead be added in addition
2020-09-08 10:00:16 +01:00
function shouldAddConfigFileQueries ( queriesInput ) {
if ( queriesInput ) {
2020-09-14 10:44:43 +01:00
return queriesInput . trimStart ( ) . substr ( 0 , 1 ) === "+" ;
2020-08-27 17:11:56 +01:00
}
return true ;
2020-08-24 15:37:07 +01:00
}
/**
* Get the default config for when the user has not supplied one.
*/
2021-07-01 11:38:14 +02:00
async function getDefaultConfig ( languagesInput , queriesInput , packsInput , dbLocation , repository , tempDir , toolCacheDir , codeQL , workspacePath , gitHubVersion , apiDetails , logger ) {
2021-06-23 15:41:52 -07:00
var _a ;
2021-05-23 16:27:46 +02:00
const languages = await getLanguages ( codeQL , languagesInput , repository , apiDetails , logger ) ;
2020-08-24 15:37:07 +01:00
const queries = { } ;
2021-05-20 18:18:55 +00:00
for ( const language of languages ) {
2021-05-21 11:52:31 +02:00
queries [ language ] = {
builtin : [ ] ,
custom : [ ] ,
} ;
2021-05-20 18:18:55 +00:00
}
2020-08-19 15:54:23 +01:00
await addDefaultQueries ( codeQL , languages , queries ) ;
2020-08-25 16:19:15 +01:00
if ( queriesInput ) {
2021-07-01 11:38:14 +02:00
await addQueriesFromWorkflow ( codeQL , queriesInput , languages , queries , tempDir , workspacePath , apiDetails , logger ) ;
2020-08-25 16:19:15 +01:00
}
2021-06-24 14:50:34 -07:00
const packs = ( _a = parsePacksFromInput ( packsInput , languages ) , ( _a !== null && _a !== void 0 ? _a : { } ) ) ;
2020-06-26 15:33:59 +01:00
return {
2020-09-14 10:44:43 +01:00
languages ,
queries ,
2020-06-26 15:33:59 +01:00
pathsIgnore : [ ] ,
2020-07-20 16:33:37 +01:00
paths : [ ] ,
2021-06-23 15:41:52 -07:00
packs ,
2020-07-20 16:33:37 +01:00
originalUserInput : { } ,
2020-08-19 15:11:49 +01:00
tempDir ,
toolCacheDir ,
2020-08-19 15:54:23 +01:00
codeQLCmd : codeQL . getPath ( ) ,
2020-11-30 16:33:38 +00:00
gitHubVersion ,
2021-05-17 10:35:09 +01:00
dbLocation : dbLocationOrDefault ( dbLocation , tempDir ) ,
2020-06-26 15:33:59 +01:00
} ;
}
2020-07-15 17:36:49 +01:00
exports . getDefaultConfig = getDefaultConfig ;
2020-06-26 15:33:59 +01:00
/**
* Load the config from the given file.
*/
2021-07-01 11:38:14 +02:00
async function loadConfig ( languagesInput , queriesInput , packsInput , configFile , dbLocation , repository , tempDir , toolCacheDir , codeQL , workspacePath , gitHubVersion , apiDetails , logger ) {
2021-06-09 13:18:27 -07:00
var _a ;
2020-06-25 13:21:46 +01:00
let parsedYAML ;
if ( isLocal ( configFile ) ) {
// Treat the config file as relative to the workspace
2021-07-01 11:38:14 +02:00
configFile = path . resolve ( workspacePath , configFile ) ;
parsedYAML = getLocalConfig ( configFile , workspacePath ) ;
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
}
2020-06-26 15:33:59 +01:00
// Validate that the 'name' property is syntactically correct,
// even though we don't use the value yet.
2020-05-26 19:23:28 +01:00
if ( NAME _PROPERTY in parsedYAML ) {
if ( typeof parsedYAML [ NAME _PROPERTY ] !== "string" ) {
throw new Error ( getNameInvalid ( configFile ) ) ;
}
if ( parsedYAML [ NAME _PROPERTY ] . length === 0 ) {
throw new Error ( getNameInvalid ( configFile ) ) ;
}
2020-05-05 17:32:58 +01:00
}
2021-05-23 16:27:46 +02:00
const languages = await getLanguages ( codeQL , languagesInput , repository , apiDetails , logger ) ;
2020-06-26 15:33:59 +01:00
const queries = { } ;
2021-05-20 18:18:55 +00:00
for ( const language of languages ) {
2021-05-21 11:52:31 +02:00
queries [ language ] = {
builtin : [ ] ,
custom : [ ] ,
} ;
2021-05-20 18:18:55 +00:00
}
2020-06-26 15:33:59 +01:00
const pathsIgnore = [ ] ;
const paths = [ ] ;
2020-07-21 11:05:23 +01:00
let disableDefaultQueries = false ;
2020-07-15 17:36:49 +01:00
if ( DISABLE _DEFAULT _QUERIES _PROPERTY in parsedYAML ) {
if ( typeof parsedYAML [ DISABLE _DEFAULT _QUERIES _PROPERTY ] !== "boolean" ) {
2020-05-26 19:23:28 +01:00
throw new Error ( getDisableDefaultQueriesInvalid ( configFile ) ) ;
}
2020-07-21 11:05:23 +01:00
disableDefaultQueries = parsedYAML [ DISABLE _DEFAULT _QUERIES _PROPERTY ] ;
}
if ( ! disableDefaultQueries ) {
2020-08-19 15:54:23 +01:00
await addDefaultQueries ( codeQL , languages , queries ) ;
2020-04-28 16:46:47 +02:00
}
2020-08-24 14:33:02 +01:00
// If queries were provided using `with` in the action configuration,
// they should take precedence over the queries in the config file
2020-08-27 17:11:56 +01:00
// unless they're prefixed with "+", in which case they supplement those
// in the config file.
2020-08-25 16:19:15 +01:00
if ( queriesInput ) {
2021-07-01 11:38:14 +02:00
await addQueriesFromWorkflow ( codeQL , queriesInput , languages , queries , tempDir , workspacePath , apiDetails , logger ) ;
2020-08-25 16:19:15 +01:00
}
2020-09-14 10:44:43 +01:00
if ( shouldAddConfigFileQueries ( queriesInput ) &&
QUERIES _PROPERTY in parsedYAML ) {
2021-06-10 09:32:02 -07:00
const queriesArr = parsedYAML [ QUERIES _PROPERTY ] ;
if ( ! Array . isArray ( queriesArr ) ) {
2020-05-26 19:23:28 +01:00
throw new Error ( getQueriesInvalid ( configFile ) ) ;
}
2021-06-10 09:32:02 -07:00
for ( const query of queriesArr ) {
2020-09-14 10:44:43 +01:00
if ( ! ( QUERIES _USES _PROPERTY in query ) ||
typeof query [ QUERIES _USES _PROPERTY ] !== "string" ) {
2020-05-26 19:23:28 +01:00
throw new Error ( getQueryUsesInvalid ( configFile ) ) ;
2020-05-05 17:32:58 +01:00
}
2021-07-01 11:38:14 +02:00
await parseQueryUses ( languages , codeQL , queries , query [ QUERIES _USES _PROPERTY ] , tempDir , workspacePath , apiDetails , logger , configFile ) ;
2020-06-26 15:33:59 +01:00
}
2020-05-05 17:32:58 +01:00
}
2020-05-26 19:23:28 +01:00
if ( PATHS _IGNORE _PROPERTY in parsedYAML ) {
2021-06-03 09:32:44 -07:00
if ( ! Array . isArray ( parsedYAML [ PATHS _IGNORE _PROPERTY ] ) ) {
2020-05-26 19:23:28 +01:00
throw new Error ( getPathsIgnoreInvalid ( configFile ) ) ;
}
2020-11-19 23:03:45 +01:00
for ( const ignorePath of parsedYAML [ PATHS _IGNORE _PROPERTY ] ) {
if ( typeof ignorePath !== "string" || ignorePath === "" ) {
2020-05-26 19:23:28 +01:00
throw new Error ( getPathsIgnoreInvalid ( configFile ) ) ;
2020-05-05 17:32:58 +01:00
}
2020-11-19 23:03:45 +01:00
pathsIgnore . push ( validateAndSanitisePath ( ignorePath , PATHS _IGNORE _PROPERTY , configFile , logger ) ) ;
2020-09-20 17:03:01 +08:00
}
2020-05-05 17:32:58 +01:00
}
2020-05-26 19:23:28 +01:00
if ( PATHS _PROPERTY in parsedYAML ) {
2021-06-03 09:32:44 -07:00
if ( ! Array . isArray ( parsedYAML [ PATHS _PROPERTY ] ) ) {
2020-05-26 19:23:28 +01:00
throw new Error ( getPathsInvalid ( configFile ) ) ;
}
2020-11-19 23:03:45 +01:00
for ( const includePath of parsedYAML [ PATHS _PROPERTY ] ) {
if ( typeof includePath !== "string" || includePath === "" ) {
2020-05-26 19:23:28 +01:00
throw new Error ( getPathsInvalid ( configFile ) ) ;
2020-05-05 17:32:58 +01:00
}
2020-11-19 23:03:45 +01:00
paths . push ( validateAndSanitisePath ( includePath , PATHS _PROPERTY , configFile , logger ) ) ;
2020-09-20 17:03:01 +08:00
}
2020-04-28 16:46:47 +02:00
}
2021-06-23 15:41:52 -07:00
const packs = parsePacks ( ( _a = parsedYAML [ PACKS _PROPERTY ] , ( _a !== null && _a !== void 0 ? _a : { } ) ) , packsInput , languages , configFile ) ;
2020-07-20 16:33:37 +01:00
return {
languages ,
queries ,
pathsIgnore ,
paths ,
2021-06-03 09:32:44 -07:00
packs ,
2020-08-19 15:11:49 +01:00
originalUserInput : parsedYAML ,
tempDir ,
toolCacheDir ,
2020-08-19 15:54:23 +01:00
codeQLCmd : codeQL . getPath ( ) ,
2020-11-30 16:33:38 +00:00
gitHubVersion ,
2021-05-17 10:35:09 +01:00
dbLocation : dbLocationOrDefault ( dbLocation , tempDir ) ,
2020-07-20 16:33:37 +01:00
} ;
2020-06-26 15:33:59 +01:00
}
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
2021-06-23 15:41:52 -07:00
function parsePacksFromConfig ( packsByLanguage , languages , configFile ) {
2021-06-03 09:32:44 -07:00
const packs = { } ;
if ( Array . isArray ( packsByLanguage ) ) {
if ( languages . length === 1 ) {
// single language analysis, so language is implicit
packsByLanguage = {
[ languages [ 0 ] ] : packsByLanguage ,
} ;
}
else {
// this is an error since multi-language analysis requires
// packs split by language
throw new Error ( getPacksInvalidSplit ( configFile ) ) ;
}
}
for ( const [ lang , packsArr ] of Object . entries ( packsByLanguage ) ) {
if ( ! Array . isArray ( packsArr ) ) {
throw new Error ( getPacksInvalid ( configFile ) ) ;
}
if ( ! languages . includes ( lang ) ) {
throw new Error ( getPacksRequireLanguage ( lang , configFile ) ) ;
}
packs [ lang ] = [ ] ;
for ( const packStr of packsArr ) {
packs [ lang ] . push ( toPackWithVersion ( packStr , configFile ) ) ;
}
}
return packs ;
}
2021-06-23 15:41:52 -07:00
exports . parsePacksFromConfig = parsePacksFromConfig ;
2021-06-24 14:50:34 -07:00
function parsePacksFromInput ( packsInput , languages ) {
2021-06-23 15:41:52 -07:00
var _a ;
if ( ! ( ( _a = packsInput ) === null || _a === void 0 ? void 0 : _a . trim ( ) ) ) {
return undefined ;
}
if ( languages . length > 1 ) {
2021-06-24 14:50:34 -07:00
throw new Error ( "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 ) {
throw new Error ( "No languages specified. Cannot process the packs input." ) ;
}
packsInput = packsInput . trim ( ) ;
if ( packsInput . startsWith ( "+" ) ) {
packsInput = packsInput . substring ( 1 ) . trim ( ) ;
if ( ! packsInput ) {
2021-06-24 14:50:34 -07:00
throw new Error ( "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 {
[ languages [ 0 ] ] : packsInput . split ( "," ) . reduce ( ( packs , pack ) => {
packs . push ( toPackWithVersion ( pack , "" ) ) ;
return packs ;
} , [ ] ) ,
} ;
}
2021-06-03 09:32:44 -07:00
function toPackWithVersion ( packStr , configFile ) {
if ( typeof packStr !== "string" ) {
throw new Error ( getPacksStrInvalid ( packStr , configFile ) ) ;
}
2021-06-23 15:41:52 -07:00
const nameWithVersion = packStr . trim ( ) . split ( "@" ) ;
2021-06-03 09:32:44 -07:00
let version ;
if ( nameWithVersion . length > 2 ||
! PACK _IDENTIFIER _PATTERN . test ( nameWithVersion [ 0 ] ) ) {
throw new Error ( getPacksStrInvalid ( packStr , configFile ) ) ;
}
else if ( nameWithVersion . length === 2 ) {
2021-06-04 13:34:55 -07:00
version = semver . clean ( nameWithVersion [ 1 ] ) || undefined ;
2021-06-03 09:32:44 -07:00
if ( ! version ) {
throw new Error ( getPacksStrInvalid ( packStr , configFile ) ) ;
}
}
return {
2021-06-23 15:41:52 -07:00
packName : nameWithVersion [ 0 ] . trim ( ) ,
2021-06-03 09:32:44 -07:00
version ,
} ;
}
2021-06-23 15:41:52 -07:00
// exported for testing
function parsePacks ( rawPacksFromConfig , rawPacksInput , languages , configFile ) {
2021-06-24 14:50:34 -07:00
const packsFromInput = parsePacksFromInput ( rawPacksInput , languages ) ;
2021-06-23 15:41:52 -07:00
const packsFomConfig = parsePacksFromConfig ( rawPacksFromConfig , languages , configFile ) ;
if ( ! packsFromInput ) {
return packsFomConfig ;
}
if ( ! shouldCombinePacks ( rawPacksInput ) ) {
return packsFromInput ;
}
return combinePacks ( packsFromInput , packsFomConfig ) ;
}
exports . parsePacks = parsePacks ;
function shouldCombinePacks ( packsInput ) {
var _a ;
return ! ! ( ( _a = packsInput ) === null || _a === void 0 ? void 0 : _a . trim ( ) . startsWith ( "+" ) ) ;
}
function combinePacks ( packs1 , packs2 ) {
const packs = { } ;
for ( const lang of Object . keys ( packs1 ) ) {
packs [ lang ] = packs1 [ lang ] . concat ( packs2 [ lang ] || [ ] ) ;
}
for ( const lang of Object . keys ( packs2 ) ) {
if ( ! packs [ lang ] ) {
packs [ lang ] = packs2 [ lang ] ;
}
}
return packs ;
}
2021-05-17 10:35:09 +01:00
function dbLocationOrDefault ( dbLocation , tempDir ) {
return dbLocation || path . resolve ( tempDir , "codeql_databases" ) ;
}
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.
*/
2021-07-01 11:38:14 +02:00
async function initConfig ( languagesInput , queriesInput , packsInput , configFile , dbLocation , repository , tempDir , toolCacheDir , codeQL , workspacePath , gitHubVersion , apiDetails , logger ) {
2021-06-03 09:32:44 -07:00
var _a , _b , _c ;
2020-06-26 15:33:59 +01:00
let config ;
// If no config file was provided create an empty one
2020-08-25 16:19:15 +01:00
if ( ! configFile ) {
2020-09-14 10:44:43 +01:00
logger . debug ( "No configuration file was provided" ) ;
2021-07-01 11:38:14 +02:00
config = await getDefaultConfig ( languagesInput , queriesInput , packsInput , dbLocation , repository , tempDir , toolCacheDir , codeQL , workspacePath , gitHubVersion , apiDetails , logger ) ;
2020-06-26 15:33:59 +01:00
}
else {
2021-07-01 11:38:14 +02:00
config = await loadConfig ( languagesInput , queriesInput , packsInput , configFile , dbLocation , repository , tempDir , toolCacheDir , codeQL , workspacePath , gitHubVersion , apiDetails , logger ) ;
2020-06-26 15:33:59 +01:00
}
2021-05-21 12:04:05 +02:00
// The list of queries should not be empty for any language. If it is then
// it is a user configuration error.
for ( const language of config . languages ) {
2021-06-04 13:12:49 -07:00
const hasBuiltinQueries = ( ( _a = config . queries [ language ] ) === null || _a === void 0 ? void 0 : _a . builtin . length ) > 0 ;
const hasCustomQueries = ( ( _b = config . queries [ language ] ) === null || _b === void 0 ? void 0 : _b . custom . length ) > 0 ;
2021-06-07 16:05:32 -07:00
const hasPacks = ( ( ( _c = config . packs [ language ] ) === null || _c === void 0 ? void 0 : _c . length ) || 0 ) > 0 ;
2021-06-04 13:12:49 -07:00
if ( ! hasPacks && ! hasBuiltinQueries && ! hasCustomQueries ) {
2021-05-21 12:04:05 +02:00
throw new Error ( ` Did not detect any queries to run for ${ language } . ` +
"Please make sure that the default queries are enabled, or you are specifying queries to run." ) ;
}
}
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 ;
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
}
2021-07-01 11:38:14 +02:00
function getLocalConfig ( configFile , workspacePath ) {
2020-06-25 13:21:46 +01:00
// Error if the config file is now outside of the workspace
2021-07-01 11:38:14 +02:00
if ( ! ( configFile + path . sep ) . startsWith ( workspacePath + path . sep ) ) {
2020-06-25 13:21:46 +01:00
throw new Error ( getConfigFileOutsideWorkspaceErrorMessage ( configFile ) ) ;
}
// Error if the file does not exist
if ( ! fs . existsSync ( configFile ) ) {
throw new Error ( getConfigFileDoesNotExistErrorMessage ( configFile ) ) ;
}
2020-09-14 10:44:43 +01:00
return yaml . safeLoad ( 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 ) {
2020-06-26 15:44:57 +01:00
throw new Error ( getConfigFileRepoFormatInvalidMessage ( configFile ) ) ;
2020-06-25 13:21:46 +01:00
}
2021-04-09 14:17:46 -07:00
const response = await api
2021-06-01 15:04:15 -07:00
. getApiClient ( apiDetails , { allowExternal : true } )
2021-04-09 14:17:46 -07:00
. 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 ) ) {
throw new Error ( getConfigFileDirectoryGivenMessage ( configFile ) ) ;
}
else {
throw new Error ( getConfigFileFormatInvalidMessage ( configFile ) ) ;
}
2020-09-14 10:44:43 +01:00
return yaml . safeLoad ( 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 ;
2020-05-13 16:31:24 +01:00
//# sourceMappingURL=config-utils.js.map